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