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