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