HostApduService.java revision 51b6322197da054715e53d02754bc81caa8fd456
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 * Reason for {@link #onDeactivated(int)}. 54 * Indicates deactivation was due to the NFC link 55 * being lost. 56 */ 57 public static final int DEACTIVATION_LINK_LOSS = 0; 58 59 /** 60 * Reason for {@link #onDeactivated(int)}. 61 * 62 * <p>Indicates deactivation was due to a different AID 63 * being selected (which implicitly deselects the AID 64 * currently active on the logical channel). 65 * 66 * <p>Note that this next AID may still be resolved to this 67 * service, in which case {@link #processCommandApdu(byte[], Bundle)} 68 * will be called again. 69 */ 70 public static final int DEACTIVATION_DESELECTED = 1; 71 72 static final String TAG = "ApduService"; 73 74 /** 75 * MSG_COMMAND_APDU is sent by NfcService when 76 * a 7816-4 command APDU has been received. 77 * 78 * @hide 79 */ 80 public static final int MSG_COMMAND_APDU = 0; 81 82 /** 83 * MSG_RESPONSE_APDU is sent to NfcService to send 84 * a response APDU back to the remote device. 85 * 86 * @hide 87 */ 88 public static final int MSG_RESPONSE_APDU = 1; 89 90 /** 91 * MSG_DEACTIVATED is sent by NfcService when 92 * the current session is finished; either because 93 * another AID was selected that resolved to 94 * another service, or because the NFC link 95 * was deactivated. 96 * 97 * @hide 98 */ 99 public static final int MSG_DEACTIVATED = 2; 100 101 /** 102 * 103 * @hide 104 */ 105 public static final int MSG_UNHANDLED = 3; 106 107 /** 108 * @hide 109 */ 110 public static final String KEY_DATA = "data"; 111 112 /** 113 * Messenger interface to NfcService for sending responses. 114 * Only accessed on main thread by the message handler. 115 * 116 * @hide 117 */ 118 Messenger mNfcService = null; 119 120 final Messenger mMessenger = new Messenger(new MsgHandler()); 121 122 final class MsgHandler extends Handler { 123 @Override 124 public void handleMessage(Message msg) { 125 switch (msg.what) { 126 case MSG_COMMAND_APDU: 127 Bundle dataBundle = msg.getData(); 128 if (dataBundle == null) { 129 return; 130 } 131 if (mNfcService == null) mNfcService = msg.replyTo; 132 133 byte[] apdu = dataBundle.getByteArray(KEY_DATA); 134 if (apdu != null) { 135 byte[] responseApdu = processCommandApdu(apdu, null); 136 if (responseApdu != null) { 137 if (mNfcService == null) { 138 Log.e(TAG, "Response not sent; service was deactivated."); 139 return; 140 } 141 Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU); 142 Bundle responseBundle = new Bundle(); 143 responseBundle.putByteArray(KEY_DATA, responseApdu); 144 responseMsg.setData(responseBundle); 145 responseMsg.replyTo = mMessenger; 146 try { 147 mNfcService.send(responseMsg); 148 } catch (RemoteException e) { 149 Log.e("TAG", "Response not sent; RemoteException calling into " + 150 "NfcService."); 151 } 152 } 153 } else { 154 Log.e(TAG, "Received MSG_COMMAND_APDU without data."); 155 } 156 break; 157 case MSG_RESPONSE_APDU: 158 if (mNfcService == null) { 159 Log.e(TAG, "Response not sent; service was deactivated."); 160 return; 161 } 162 try { 163 msg.replyTo = mMessenger; 164 mNfcService.send(msg); 165 } catch (RemoteException e) { 166 Log.e(TAG, "RemoteException calling into NfcService."); 167 } 168 break; 169 case MSG_DEACTIVATED: 170 // Make sure we won't call into NfcService again 171 mNfcService = null; 172 onDeactivated(msg.arg1); 173 break; 174 case MSG_UNHANDLED: 175 if (mNfcService == null) { 176 Log.e(TAG, "notifyUnhandled 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 default: 187 super.handleMessage(msg); 188 } 189 } 190 } 191 192 @Override 193 public final IBinder onBind(Intent intent) { 194 return mMessenger.getBinder(); 195 } 196 197 /** 198 * Sends a response APDU back to the remote device. 199 * 200 * <p>Note: this method may be called from any thread and will not block. 201 * @param responseApdu A byte-array containing the reponse APDU. 202 */ 203 public final void sendResponseApdu(byte[] responseApdu) { 204 Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU); 205 Bundle dataBundle = new Bundle(); 206 dataBundle.putByteArray(KEY_DATA, responseApdu); 207 responseMsg.setData(dataBundle); 208 try { 209 mMessenger.send(responseMsg); 210 } catch (RemoteException e) { 211 Log.e("TAG", "Local messenger has died."); 212 } 213 } 214 215 /** 216 * Calling this method allows the service to tell the OS 217 * that it won't be able to complete this transaction - 218 * for example, because it requires data connectivity 219 * that is not present at that moment. 220 * 221 * The OS may use this indication to give the user a list 222 * of alternative applications that can handle the last 223 * AID that was selected. If the user would select an 224 * application from the list, that action by itself 225 * will not cause the default to be changed; the selected 226 * application will be invoked for the next tap only. 227 * 228 * If there are no other applications that can handle 229 * this transaction, the OS will show an error dialog 230 * indicating your service could not complete the 231 * transaction. 232 * 233 * <p>Note: this method may be called anywhere between 234 * the first {@link #processCommandApdu(byte[], Bundle)} 235 * call and a {@link #onDeactivated(int)} call. 236 */ 237 public final void notifyUnhandled() { 238 Message unhandledMsg = Message.obtain(null, MSG_UNHANDLED); 239 try { 240 mMessenger.send(unhandledMsg); 241 } catch (RemoteException e) { 242 Log.e("TAG", "Local messenger has died."); 243 } 244 } 245 246 247 /** 248 * <p>This method will be called when a command APDU has been received 249 * from a remote device. A response APDU can be provided directly 250 * by returning a byte-array in this method. Note that in general 251 * response APDUs must be sent as quickly as possible, given the fact 252 * that the user is likely holding his device over an NFC reader 253 * when this method is called. 254 * 255 * <p class="note">If there are multiple services that have registered for the same 256 * AIDs in their meta-data entry, you will only get called if the user has 257 * explicitly selected your service, either as a default or just for the next tap. 258 * 259 * <p class="note">This method is running on the main thread of your application. 260 * If you cannot return a response APDU immediately, return null 261 * and use the {@link #sendResponseApdu(byte[])} method later. 262 * 263 * @param commandApdu The APDU that received from the remote device 264 * @param extras A bundle containing extra data. May be null. 265 * @return a byte-array containing the response APDU, or null if no 266 * response APDU can be sent at this point. 267 */ 268 public abstract byte[] processCommandApdu(byte[] commandApdu, Bundle extras); 269 270 /** 271 * This method will be called in two possible scenarios: 272 * <li>The NFC link has been deactivated or lost 273 * <li>A different AID has been selected and was resolved to a different 274 * service component 275 * @param reason Either {@link #DEACTIVATION_LINK_LOSS} or {@link #DEACTIVATION_DESELECTED} 276 */ 277 public abstract void onDeactivated(int reason); 278} 279