HostApduService.java revision 5eaa8970aa86fa32a6cbb47e0681000ed2d22c57
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#ApduService apdu-service}></code> and 32 * <code><{@link android.R.styleable#AidFilter aid-filter}></code>. 33 * <p class="note">The Android platform currently only supports a single 34 * logical channel. 35 */ 36public abstract class HostApduService extends Service { 37 /** 38 * The {@link Intent} that must be declared as handled by the service. 39 */ 40 @SdkConstant(SdkConstantType.SERVICE_ACTION) 41 public static final String SERVICE_INTERFACE = 42 "android.nfc.HostApduService"; 43 44 /** 45 * The name of the meta-data element that contains 46 * more information about this service. 47 */ 48 public static final String SERVICE_META_DATA = "android.nfc.HostApduService"; 49 50 /** 51 * Reason for {@link #onDeactivated(int)}. 52 * Indicates deactivation was due to the NFC link 53 * being lost. 54 */ 55 public static final int DEACTIVATION_LINK_LOSS = 0; 56 57 /** 58 * Reason for {@link #onDeactivated(int)}. 59 * 60 * <p>Indicates deactivation was due to a different AID 61 * being selected (which implicitly deselects the AID 62 * currently active on the logical channel). 63 * 64 * <p>Note that this next AID may still be resolved to this 65 * service, in which case {@link #processCommandApdu(byte[], int)} 66 * will be called again. 67 */ 68 public static final int DEACTIVATION_DESELECTED = 1; 69 70 static final String TAG = "ApduService"; 71 72 /** 73 * MSG_COMMAND_APDU is sent by NfcService when 74 * a 7816-4 command APDU has been received. 75 * 76 * @hide 77 */ 78 public static final int MSG_COMMAND_APDU = 0; 79 80 /** 81 * MSG_RESPONSE_APDU is sent to NfcService to send 82 * a response APDU back to the remote device. 83 * 84 * @hide 85 */ 86 public static final int MSG_RESPONSE_APDU = 1; 87 88 /** 89 * MSG_DEACTIVATED is sent by NfcService when 90 * the current session is finished; either because 91 * another AID was selected that resolved to 92 * another service, or because the NFC link 93 * was deactivated. 94 * 95 * @hide 96 */ 97 public static final int MSG_DEACTIVATED = 2; 98 99 /** 100 * @hide 101 */ 102 public static final String KEY_DATA = "data"; 103 104 /** 105 * Messenger interface to NfcService for sending responses. 106 * Only accessed on main thread by the message handler. 107 */ 108 Messenger mNfcService = null; 109 110 final Messenger mMessenger = new Messenger(new MsgHandler()); 111 112 final class MsgHandler extends Handler { 113 @Override 114 public void handleMessage(Message msg) { 115 switch (msg.what) { 116 case MSG_COMMAND_APDU: 117 Bundle dataBundle = msg.getData(); 118 if (dataBundle == null) { 119 return; 120 } 121 if (mNfcService == null) mNfcService = msg.replyTo; 122 123 byte[] apdu = dataBundle.getByteArray(KEY_DATA); 124 if (apdu != null) { 125 byte[] responseApdu = processCommandApdu(apdu, 0); 126 if (responseApdu != null) { 127 if (mNfcService == null) { 128 Log.e(TAG, "Response not sent; service was deactivated."); 129 return; 130 } 131 Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU); 132 Bundle responseBundle = new Bundle(); 133 responseBundle.putByteArray(KEY_DATA, responseApdu); 134 responseMsg.setData(responseBundle); 135 try { 136 mNfcService.send(responseMsg); 137 } catch (RemoteException e) { 138 Log.e("TAG", "Response not sent; RemoteException calling into " + 139 "NfcService."); 140 } 141 } 142 } else { 143 Log.e(TAG, "Received MSG_COMMAND_APDU without data."); 144 } 145 break; 146 case MSG_RESPONSE_APDU: 147 if (mNfcService == null) { 148 Log.e(TAG, "Response not sent; service was deactivated."); 149 return; 150 } 151 try { 152 mNfcService.send(msg); 153 } catch (RemoteException e) { 154 Log.e(TAG, "RemoteException calling into NfcService."); 155 } 156 break; 157 case MSG_DEACTIVATED: 158 // Make sure we won't call into NfcService again 159 mNfcService = null; 160 onDeactivated(msg.arg1); 161 break; 162 default: 163 super.handleMessage(msg); 164 } 165 } 166 } 167 168 @Override 169 public final IBinder onBind(Intent intent) { 170 return mMessenger.getBinder(); 171 } 172 173 /** 174 * Sends a response APDU back to the remote device. 175 * 176 * <p>Note: this method may be called from any thread and will not block. 177 * @param responseApdu A byte-array containing the reponse APDU. 178 */ 179 public final void sendResponseApdu(byte[] responseApdu) { 180 Message responseMsg = Message.obtain(null, MSG_RESPONSE_APDU); 181 Bundle dataBundle = new Bundle(); 182 dataBundle.putByteArray(KEY_DATA, responseApdu); 183 responseMsg.setData(dataBundle); 184 try { 185 mMessenger.send(responseMsg); 186 } catch (RemoteException e) { 187 Log.e("TAG", "Local messenger has died."); 188 } 189 } 190 191 /** 192 * <p>This method will be called when a command APDU has been received 193 * from a remote device. A response APDU can be provided directly 194 * by returning a byte-array in this method. Note that in general 195 * response APDUs must be sent as quickly as possible, given the fact 196 * that the user is likely holding his device over an NFC reader 197 * when this method is called. 198 * 199 * <p class="note">If there are multiple services that have registered for the same 200 * AIDs in their meta-data entry, you will only get called if the user has 201 * explicitly selected your service, either as a default or just for the next tap. 202 * 203 * <p class="note">This method is running on the main thread of your application. 204 * If you cannot return a response APDU immediately, return null 205 * and use the {@link #sendResponseApdu(byte[])} method later. 206 * 207 * @param commandApdu 208 * @param flags 209 * @return a byte-array containing the response APDU, or null if no 210 * response APDU can be sent at this point. 211 */ 212 public abstract byte[] processCommandApdu(byte[] commandApdu, int flags); 213 214 /** 215 * This method will be called in two possible scenarios: 216 * <li>The NFC link has been deactivated or lost 217 * <li>A different AID has been selected and was resolved to a different 218 * service component 219 * @param reason Either {@link #DEACTIVATION_LINK_LOSS} or {@link #DEACTIVATION_DESELECTED} 220 */ 221 public abstract void onDeactivated(int reason); 222} 223