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