1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.nfc.cardemulation; 18 19 import android.app.ActivityManager; 20 import android.app.KeyguardManager; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.ServiceConnection; 25 import android.nfc.cardemulation.ApduServiceInfo; 26 import android.nfc.cardemulation.CardEmulation; 27 import android.nfc.cardemulation.HostApduService; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.IBinder; 31 import android.os.Message; 32 import android.os.Messenger; 33 import android.os.RemoteException; 34 import android.os.UserHandle; 35 import android.util.Log; 36 37 import com.android.nfc.NfcService; 38 import com.android.nfc.cardemulation.RegisteredAidCache.AidResolveInfo; 39 40 import java.io.FileDescriptor; 41 import java.io.PrintWriter; 42 import java.util.ArrayList; 43 44 public class HostEmulationManager { 45 static final String TAG = "HostEmulationManager"; 46 static final boolean DBG = false; 47 48 static final int STATE_IDLE = 0; 49 static final int STATE_W4_SELECT = 1; 50 static final int STATE_W4_SERVICE = 2; 51 static final int STATE_W4_DEACTIVATE = 3; 52 static final int STATE_XFER = 4; 53 54 /** Minimum AID lenth as per ISO7816 */ 55 static final int MINIMUM_AID_LENGTH = 5; 56 57 /** Length of Select APDU header including length byte */ 58 static final int SELECT_APDU_HDR_LENGTH = 5; 59 60 static final byte INSTR_SELECT = (byte)0xA4; 61 62 static final String ANDROID_HCE_AID = "A000000476416E64726F6964484345"; 63 static final byte[] ANDROID_HCE_RESPONSE = {0x14, (byte)0x81, 0x00, 0x00, (byte)0x90, 0x00}; 64 65 static final byte[] AID_NOT_FOUND = {0x6A, (byte)0x82}; 66 static final byte[] UNKNOWN_ERROR = {0x6F, 0x00}; 67 68 final Context mContext; 69 final RegisteredAidCache mAidCache; 70 final Messenger mMessenger = new Messenger (new MessageHandler()); 71 final KeyguardManager mKeyguard; 72 final Object mLock; 73 74 // All variables below protected by mLock 75 76 // Variables below are for a non-payment service, 77 // that is typically only bound in the STATE_XFER state. 78 Messenger mService; 79 boolean mServiceBound; 80 ComponentName mServiceName; 81 82 // Variables below are for a payment service, 83 // which is typically bound persistently to improve on 84 // latency. 85 Messenger mPaymentService; 86 boolean mPaymentServiceBound; 87 ComponentName mPaymentServiceName; 88 89 // mActiveService denotes the service interface 90 // that is the current active one, until a new SELECT AID 91 // comes in that may be resolved to a different service. 92 // On deactivation, mActiveService stops being valid. 93 Messenger mActiveService; 94 ComponentName mActiveServiceName; 95 96 String mLastSelectedAid; 97 int mState; 98 byte[] mSelectApdu; 99 HostEmulationManager(Context context, RegisteredAidCache aidCache)100 public HostEmulationManager(Context context, RegisteredAidCache aidCache) { 101 mContext = context; 102 mLock = new Object(); 103 mAidCache = aidCache; 104 mState = STATE_IDLE; 105 mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); 106 } 107 onPreferredPaymentServiceChanged(ComponentName service)108 public void onPreferredPaymentServiceChanged(ComponentName service) { 109 synchronized (mLock) { 110 if (service != null) { 111 bindPaymentServiceLocked(ActivityManager.getCurrentUser(), service); 112 } else { 113 unbindPaymentServiceLocked(); 114 } 115 } 116 } 117 onPreferredForegroundServiceChanged(ComponentName service)118 public void onPreferredForegroundServiceChanged(ComponentName service) { 119 synchronized (mLock) { 120 if (service != null) { 121 bindServiceIfNeededLocked(service); 122 } else { 123 unbindServiceIfNeededLocked(); 124 } 125 } 126 } 127 onHostEmulationActivated()128 public void onHostEmulationActivated() { 129 Log.d(TAG, "notifyHostEmulationActivated"); 130 synchronized (mLock) { 131 // Regardless of what happens, if we're having a tap again 132 // activity up, close it 133 Intent intent = new Intent(TapAgainDialog.ACTION_CLOSE); 134 intent.setPackage("com.android.nfc"); 135 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 136 if (mState != STATE_IDLE) { 137 Log.e(TAG, "Got activation event in non-idle state"); 138 } 139 mState = STATE_W4_SELECT; 140 } 141 } 142 onHostEmulationData(byte[] data)143 public void onHostEmulationData(byte[] data) { 144 Log.d(TAG, "notifyHostEmulationData"); 145 String selectAid = findSelectAid(data); 146 ComponentName resolvedService = null; 147 synchronized (mLock) { 148 if (mState == STATE_IDLE) { 149 Log.e(TAG, "Got data in idle state."); 150 return; 151 } else if (mState == STATE_W4_DEACTIVATE) { 152 Log.e(TAG, "Dropping APDU in STATE_W4_DECTIVATE"); 153 return; 154 } 155 if (selectAid != null) { 156 if (selectAid.equals(ANDROID_HCE_AID)) { 157 NfcService.getInstance().sendData(ANDROID_HCE_RESPONSE); 158 return; 159 } 160 AidResolveInfo resolveInfo = mAidCache.resolveAid(selectAid); 161 if (resolveInfo == null || resolveInfo.services.size() == 0) { 162 // Tell the remote we don't handle this AID 163 NfcService.getInstance().sendData(AID_NOT_FOUND); 164 return; 165 } 166 mLastSelectedAid = selectAid; 167 if (resolveInfo.defaultService != null) { 168 // Resolve to default 169 // Check if resolvedService requires unlock 170 ApduServiceInfo defaultServiceInfo = resolveInfo.defaultService; 171 if (defaultServiceInfo.requiresUnlock() && 172 mKeyguard.isKeyguardLocked() && mKeyguard.isKeyguardSecure()) { 173 // Just ignore all future APDUs until next tap 174 mState = STATE_W4_DEACTIVATE; 175 launchTapAgain(resolveInfo.defaultService, resolveInfo.category); 176 return; 177 } 178 // In no circumstance should this be an OffHostService - 179 // we should never get this AID on the host in the first place 180 if (!defaultServiceInfo.isOnHost()) { 181 Log.e(TAG, "AID that was meant to go off-host was routed to host." + 182 " Check routing table configuration."); 183 NfcService.getInstance().sendData(AID_NOT_FOUND); 184 return; 185 } 186 resolvedService = defaultServiceInfo.getComponent(); 187 } else if (mActiveServiceName != null) { 188 for (ApduServiceInfo serviceInfo : resolveInfo.services) { 189 if (mActiveServiceName.equals(serviceInfo.getComponent())) { 190 resolvedService = mActiveServiceName; 191 break; 192 } 193 } 194 } 195 if (resolvedService == null) { 196 // We have no default, and either one or more services. 197 // Ask the user to confirm. 198 // Just ignore all future APDUs until we resolve to only one 199 mState = STATE_W4_DEACTIVATE; 200 launchResolver((ArrayList<ApduServiceInfo>)resolveInfo.services, null, 201 resolveInfo.category); 202 return; 203 } 204 } 205 switch (mState) { 206 case STATE_W4_SELECT: 207 if (selectAid != null) { 208 Messenger existingService = bindServiceIfNeededLocked(resolvedService); 209 if (existingService != null) { 210 Log.d(TAG, "Binding to existing service"); 211 mState = STATE_XFER; 212 sendDataToServiceLocked(existingService, data); 213 } else { 214 // Waiting for service to be bound 215 Log.d(TAG, "Waiting for new service."); 216 // Queue SELECT APDU to be used 217 mSelectApdu = data; 218 mState = STATE_W4_SERVICE; 219 } 220 } else { 221 Log.d(TAG, "Dropping non-select APDU in STATE_W4_SELECT"); 222 NfcService.getInstance().sendData(UNKNOWN_ERROR); 223 } 224 break; 225 case STATE_W4_SERVICE: 226 Log.d(TAG, "Unexpected APDU in STATE_W4_SERVICE"); 227 break; 228 case STATE_XFER: 229 if (selectAid != null) { 230 Messenger existingService = bindServiceIfNeededLocked(resolvedService); 231 if (existingService != null) { 232 sendDataToServiceLocked(existingService, data); 233 mState = STATE_XFER; 234 } else { 235 // Waiting for service to be bound 236 mSelectApdu = data; 237 mState = STATE_W4_SERVICE; 238 } 239 } else if (mActiveService != null) { 240 // Regular APDU data 241 sendDataToServiceLocked(mActiveService, data); 242 } else { 243 // No SELECT AID and no active service. 244 Log.d(TAG, "Service no longer bound, dropping APDU"); 245 } 246 break; 247 } 248 } 249 } 250 onHostEmulationDeactivated()251 public void onHostEmulationDeactivated() { 252 Log.d(TAG, "notifyHostEmulationDeactivated"); 253 synchronized (mLock) { 254 if (mState == STATE_IDLE) { 255 Log.e(TAG, "Got deactivation event while in idle state"); 256 } 257 sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_LINK_LOSS); 258 mActiveService = null; 259 mActiveServiceName = null; 260 unbindServiceIfNeededLocked(); 261 mState = STATE_IDLE; 262 } 263 } 264 onOffHostAidSelected()265 public void onOffHostAidSelected() { 266 Log.d(TAG, "notifyOffHostAidSelected"); 267 synchronized (mLock) { 268 if (mState != STATE_XFER || mActiveService == null) { 269 // Don't bother telling, we're not bound to any service yet 270 } else { 271 sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_DESELECTED); 272 } 273 mActiveService = null; 274 mActiveServiceName = null; 275 unbindServiceIfNeededLocked(); 276 mState = STATE_W4_SELECT; 277 278 //close the TapAgainDialog 279 Intent intent = new Intent(TapAgainDialog.ACTION_CLOSE); 280 intent.setPackage("com.android.nfc"); 281 mContext.sendBroadcastAsUser(intent, UserHandle.ALL); 282 } 283 } 284 bindServiceIfNeededLocked(ComponentName service)285 Messenger bindServiceIfNeededLocked(ComponentName service) { 286 if (mPaymentServiceBound && mPaymentServiceName.equals(service)) { 287 Log.d(TAG, "Service already bound as payment service."); 288 return mPaymentService; 289 } else if (mServiceBound && mServiceName.equals(service)) { 290 Log.d(TAG, "Service already bound as regular service."); 291 return mService; 292 } else { 293 Log.d(TAG, "Binding to service " + service); 294 unbindServiceIfNeededLocked(); 295 Intent aidIntent = new Intent(HostApduService.SERVICE_INTERFACE); 296 aidIntent.setComponent(service); 297 if (mContext.bindServiceAsUser(aidIntent, mConnection, 298 Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) { 299 } else { 300 Log.e(TAG, "Could not bind service."); 301 } 302 return null; 303 } 304 } 305 sendDataToServiceLocked(Messenger service, byte[] data)306 void sendDataToServiceLocked(Messenger service, byte[] data) { 307 if (service != mActiveService) { 308 sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_DESELECTED); 309 mActiveService = service; 310 if (service.equals(mPaymentService)) { 311 mActiveServiceName = mPaymentServiceName; 312 } else { 313 mActiveServiceName = mServiceName; 314 } 315 } 316 Message msg = Message.obtain(null, HostApduService.MSG_COMMAND_APDU); 317 Bundle dataBundle = new Bundle(); 318 dataBundle.putByteArray("data", data); 319 msg.setData(dataBundle); 320 msg.replyTo = mMessenger; 321 try { 322 mActiveService.send(msg); 323 } catch (RemoteException e) { 324 Log.e(TAG, "Remote service has died, dropping APDU"); 325 } 326 } 327 sendDeactivateToActiveServiceLocked(int reason)328 void sendDeactivateToActiveServiceLocked(int reason) { 329 if (mActiveService == null) return; 330 Message msg = Message.obtain(null, HostApduService.MSG_DEACTIVATED); 331 msg.arg1 = reason; 332 try { 333 mActiveService.send(msg); 334 } catch (RemoteException e) { 335 // Don't care 336 } 337 } 338 unbindPaymentServiceLocked()339 void unbindPaymentServiceLocked() { 340 if (mPaymentServiceBound) { 341 mContext.unbindService(mPaymentConnection); 342 mPaymentServiceBound = false; 343 mPaymentService = null; 344 mPaymentServiceName = null; 345 } 346 } 347 bindPaymentServiceLocked(int userId, ComponentName service)348 void bindPaymentServiceLocked(int userId, ComponentName service) { 349 unbindPaymentServiceLocked(); 350 351 Intent intent = new Intent(HostApduService.SERVICE_INTERFACE); 352 intent.setComponent(service); 353 if (!mContext.bindServiceAsUser(intent, mPaymentConnection, 354 Context.BIND_AUTO_CREATE, new UserHandle(userId))) { 355 Log.e(TAG, "Could not bind (persistent) payment service."); 356 } 357 } 358 unbindServiceIfNeededLocked()359 void unbindServiceIfNeededLocked() { 360 if (mServiceBound) { 361 Log.d(TAG, "Unbinding from service " + mServiceName); 362 mContext.unbindService(mConnection); 363 mServiceBound = false; 364 mService = null; 365 mServiceName = null; 366 } 367 } 368 launchTapAgain(ApduServiceInfo service, String category)369 void launchTapAgain(ApduServiceInfo service, String category) { 370 Intent dialogIntent = new Intent(mContext, TapAgainDialog.class); 371 dialogIntent.putExtra(TapAgainDialog.EXTRA_CATEGORY, category); 372 dialogIntent.putExtra(TapAgainDialog.EXTRA_APDU_SERVICE, service); 373 dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 374 mContext.startActivityAsUser(dialogIntent, UserHandle.CURRENT); 375 } 376 launchResolver(ArrayList<ApduServiceInfo> services, ComponentName failedComponent, String category)377 void launchResolver(ArrayList<ApduServiceInfo> services, ComponentName failedComponent, 378 String category) { 379 Intent intent = new Intent(mContext, AppChooserActivity.class); 380 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 381 intent.putParcelableArrayListExtra(AppChooserActivity.EXTRA_APDU_SERVICES, services); 382 intent.putExtra(AppChooserActivity.EXTRA_CATEGORY, category); 383 if (failedComponent != null) { 384 intent.putExtra(AppChooserActivity.EXTRA_FAILED_COMPONENT, failedComponent); 385 } 386 mContext.startActivityAsUser(intent, UserHandle.CURRENT); 387 } 388 findSelectAid(byte[] data)389 String findSelectAid(byte[] data) { 390 if (data == null || data.length < SELECT_APDU_HDR_LENGTH + MINIMUM_AID_LENGTH) { 391 if (DBG) Log.d(TAG, "Data size too small for SELECT APDU"); 392 return null; 393 } 394 // To accept a SELECT AID for dispatch, we require the following: 395 // Class byte must be 0x00: logical channel set to zero, no secure messaging, no chaining 396 // Instruction byte must be 0xA4: SELECT instruction 397 // P1: must be 0x04: select by application identifier 398 // P2: File control information is only relevant for higher-level application, 399 // and we only support "first or only occurrence". 400 if (data[0] == 0x00 && data[1] == INSTR_SELECT && data[2] == 0x04) { 401 if (data[3] != 0x00) { 402 Log.d(TAG, "Selecting next, last or previous AID occurrence is not supported"); 403 } 404 int aidLength = data[4]; 405 if (data.length < SELECT_APDU_HDR_LENGTH + aidLength) { 406 return null; 407 } 408 return bytesToString(data, SELECT_APDU_HDR_LENGTH, aidLength); 409 } 410 return null; 411 } 412 413 private ServiceConnection mPaymentConnection = new ServiceConnection() { 414 @Override 415 public void onServiceConnected(ComponentName name, IBinder service) { 416 synchronized (mLock) { 417 mPaymentServiceName = name; 418 mPaymentService = new Messenger(service); 419 mPaymentServiceBound = true; 420 } 421 } 422 423 @Override 424 public void onServiceDisconnected(ComponentName name) { 425 synchronized (mLock) { 426 mPaymentService = null; 427 mPaymentServiceBound = false; 428 mPaymentServiceName = null; 429 } 430 } 431 }; 432 433 private ServiceConnection mConnection = new ServiceConnection() { 434 @Override 435 public void onServiceConnected(ComponentName name, IBinder service) { 436 synchronized (mLock) { 437 mService = new Messenger(service); 438 mServiceBound = true; 439 mServiceName = name; 440 Log.d(TAG, "Service bound"); 441 mState = STATE_XFER; 442 // Send pending select APDU 443 if (mSelectApdu != null) { 444 sendDataToServiceLocked(mService, mSelectApdu); 445 mSelectApdu = null; 446 } 447 } 448 } 449 450 @Override 451 public void onServiceDisconnected(ComponentName name) { 452 synchronized (mLock) { 453 Log.d(TAG, "Service unbound"); 454 mService = null; 455 mServiceBound = false; 456 } 457 } 458 }; 459 460 class MessageHandler extends Handler { 461 @Override handleMessage(Message msg)462 public void handleMessage(Message msg) { 463 synchronized(mLock) { 464 if (mActiveService == null) { 465 Log.d(TAG, "Dropping service response message; service no longer active."); 466 return; 467 } else if (!msg.replyTo.getBinder().equals(mActiveService.getBinder())) { 468 Log.d(TAG, "Dropping service response message; service no longer bound."); 469 return; 470 } 471 } 472 if (msg.what == HostApduService.MSG_RESPONSE_APDU) { 473 Bundle dataBundle = msg.getData(); 474 if (dataBundle == null) { 475 return; 476 } 477 byte[] data = dataBundle.getByteArray("data"); 478 if (data == null || data.length == 0) { 479 Log.e(TAG, "Dropping empty R-APDU"); 480 return; 481 } 482 int state; 483 synchronized(mLock) { 484 state = mState; 485 } 486 if (state == STATE_XFER) { 487 Log.d(TAG, "Sending data"); 488 NfcService.getInstance().sendData(data); 489 } else { 490 Log.d(TAG, "Dropping data, wrong state " + Integer.toString(state)); 491 } 492 } else if (msg.what == HostApduService.MSG_UNHANDLED) { 493 synchronized (mLock) { 494 AidResolveInfo resolveInfo = mAidCache.resolveAid(mLastSelectedAid); 495 boolean isPayment = false; 496 if (resolveInfo.services.size() > 0) { 497 launchResolver((ArrayList<ApduServiceInfo>)resolveInfo.services, 498 mActiveServiceName, resolveInfo.category); 499 } 500 } 501 } 502 } 503 } 504 bytesToString(byte[] bytes, int offset, int length)505 static String bytesToString(byte[] bytes, int offset, int length) { 506 final char[] hexChars = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; 507 char[] chars = new char[length * 2]; 508 int byteValue; 509 for (int j = 0; j < length; j++) { 510 byteValue = bytes[offset + j] & 0xFF; 511 chars[j * 2] = hexChars[byteValue >>> 4]; 512 chars[j * 2 + 1] = hexChars[byteValue & 0x0F]; 513 } 514 return new String(chars); 515 } 516 dump(FileDescriptor fd, PrintWriter pw, String[] args)517 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 518 pw.println("Bound HCE-A/HCE-B services: "); 519 if (mPaymentServiceBound) { 520 pw.println(" payment: " + mPaymentServiceName); 521 } 522 if (mServiceBound) { 523 pw.println(" other: " + mServiceName); 524 } 525 } 526 } 527