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