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