1 /* 2 * Copyright (C) 2014 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.content.ComponentName; 21 import android.content.Context; 22 import android.nfc.cardemulation.ApduServiceInfo; 23 import android.nfc.cardemulation.CardEmulation; 24 import android.util.Log; 25 26 import com.google.android.collect.Maps; 27 28 import java.io.FileDescriptor; 29 import java.io.PrintWriter; 30 import java.util.ArrayList; 31 import java.util.Collection; 32 import java.util.HashMap; 33 import java.util.HashSet; 34 import java.util.List; 35 import java.util.Map; 36 import java.util.NavigableMap; 37 import java.util.PriorityQueue; 38 import java.util.TreeMap; 39 40 public class RegisteredAidCache { 41 static final String TAG = "RegisteredAidCache"; 42 43 static final boolean DBG = false; 44 45 // mAidServices maps AIDs to services that have registered them. 46 // It's a TreeMap in order to be able to quickly select subsets 47 // of AIDs that conflict with each other. 48 final TreeMap<String, ArrayList<ServiceAidInfo>> mAidServices = 49 new TreeMap<String, ArrayList<ServiceAidInfo>>(); 50 51 // mAidCache is a lookup table for quickly mapping an exact or prefix AID to one or 52 // more handling services. It differs from mAidServices in the sense that it 53 // has already accounted for defaults, and hence its return value 54 // is authoritative for the current set of services and defaults. 55 // It is only valid for the current user. 56 final TreeMap<String, AidResolveInfo> mAidCache = new TreeMap<String, AidResolveInfo>(); 57 58 // Represents a single AID registration of a service 59 final class ServiceAidInfo { 60 ApduServiceInfo service; 61 String aid; 62 String category; 63 64 @Override 65 public String toString() { 66 return "ServiceAidInfo{" + 67 "service=" + service.getComponent() + 68 ", aid='" + aid + '\'' + 69 ", category='" + category + '\'' + 70 '}'; 71 } 72 73 @Override 74 public boolean equals(Object o) { 75 if (this == o) return true; 76 if (o == null || getClass() != o.getClass()) return false; 77 78 ServiceAidInfo that = (ServiceAidInfo) o; 79 80 if (!aid.equals(that.aid)) return false; 81 if (!category.equals(that.category)) return false; 82 if (!service.equals(that.service)) return false; 83 84 return true; 85 } 86 87 @Override 88 public int hashCode() { 89 int result = service.hashCode(); 90 result = 31 * result + aid.hashCode(); 91 result = 31 * result + category.hashCode(); 92 return result; 93 } 94 } 95 96 // Represents a list of services, an optional default and a category that 97 // an AID was resolved to. 98 final class AidResolveInfo { 99 List<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>(); 100 ApduServiceInfo defaultService = null; 101 String category = null; 102 boolean mustRoute = true; // Whether this AID should be routed at all 103 104 @Override 105 public String toString() { 106 return "AidResolveInfo{" + 107 "services=" + services + 108 ", defaultService=" + defaultService + 109 ", category='" + category + '\'' + 110 ", mustRoute=" + mustRoute + 111 '}'; 112 } 113 } 114 115 final AidResolveInfo EMPTY_RESOLVE_INFO = new AidResolveInfo(); 116 117 final Context mContext; 118 final AidRoutingManager mRoutingManager; 119 120 final Object mLock = new Object(); 121 122 ComponentName mPreferredPaymentService; 123 ComponentName mPreferredForegroundService; 124 125 boolean mNfcEnabled = false; 126 boolean mSupportsPrefixes = false; 127 128 public RegisteredAidCache(Context context) { 129 mContext = context; 130 mRoutingManager = new AidRoutingManager(); 131 mPreferredPaymentService = null; 132 mPreferredForegroundService = null; 133 mSupportsPrefixes = mRoutingManager.supportsAidPrefixRouting(); 134 if (mSupportsPrefixes) { 135 if (DBG) Log.d(TAG, "Controller supports AID prefix routing"); 136 } 137 } 138 139 public AidResolveInfo resolveAid(String aid) { 140 synchronized (mLock) { 141 if (DBG) Log.d(TAG, "resolveAid: resolving AID " + aid); 142 if (aid.length() < 10) { 143 Log.e(TAG, "AID selected with fewer than 5 bytes."); 144 return EMPTY_RESOLVE_INFO; 145 } 146 AidResolveInfo resolveInfo = new AidResolveInfo(); 147 if (mSupportsPrefixes) { 148 // Our AID cache may contain prefixes which also match this AID, 149 // so we must find all potential prefixes and merge the ResolveInfo 150 // of those prefixes plus any exact match in a single result. 151 String shortestAidMatch = aid.substring(0, 10); // Minimum AID length is 5 bytes 152 String longestAidMatch = aid + "*"; // Longest potential matching AID 153 154 if (DBG) Log.d(TAG, "Finding AID registrations in range [" + shortestAidMatch + 155 " - " + longestAidMatch + "]"); 156 NavigableMap<String, AidResolveInfo> matchingAids = 157 mAidCache.subMap(shortestAidMatch, true, longestAidMatch, true); 158 159 resolveInfo.category = CardEmulation.CATEGORY_OTHER; 160 for (Map.Entry<String, AidResolveInfo> entry : matchingAids.entrySet()) { 161 boolean isPrefix = isPrefix(entry.getKey()); 162 String entryAid = isPrefix ? entry.getKey().substring(0, 163 entry.getKey().length() - 1) : entry.getKey(); // Cut off '*' if prefix 164 if (entryAid.equalsIgnoreCase(aid) || (isPrefix && aid.startsWith(entryAid))) { 165 if (DBG) Log.d(TAG, "resolveAid: AID " + entryAid + " matches."); 166 AidResolveInfo entryResolveInfo = entry.getValue(); 167 if (entryResolveInfo.defaultService != null) { 168 if (resolveInfo.defaultService != null) { 169 // This shouldn't happen; for every prefix we have only one 170 // default service. 171 Log.e(TAG, "Different defaults for conflicting AIDs!"); 172 } 173 resolveInfo.defaultService = entryResolveInfo.defaultService; 174 resolveInfo.category = entryResolveInfo.category; 175 } 176 for (ApduServiceInfo serviceInfo : entryResolveInfo.services) { 177 if (!resolveInfo.services.contains(serviceInfo)) { 178 resolveInfo.services.add(serviceInfo); 179 } 180 } 181 } 182 } 183 } else { 184 resolveInfo = mAidCache.get(aid); 185 } 186 if (DBG) Log.d(TAG, "Resolved to: " + resolveInfo); 187 return resolveInfo; 188 } 189 } 190 191 public boolean supportsAidPrefixRegistration() { 192 return mSupportsPrefixes; 193 } 194 195 public boolean isDefaultServiceForAid(int userId, ComponentName service, String aid) { 196 AidResolveInfo resolveInfo = resolveAid(aid); 197 if (resolveInfo == null || resolveInfo.services == null || 198 resolveInfo.services.size() == 0) { 199 return false; 200 } 201 202 if (resolveInfo.defaultService != null) { 203 return service.equals(resolveInfo.defaultService.getComponent()); 204 } else if (resolveInfo.services.size() == 1) { 205 return service.equals(resolveInfo.services.get(0).getComponent()); 206 } else { 207 // More than one service, not the default 208 return false; 209 } 210 } 211 212 /** 213 * Resolves a conflict between multiple services handling the same 214 * AIDs. Note that the AID itself is not an input to the decision 215 * process - the algorithm just looks at the competing services 216 * and what preferences the user has indicated. In short, it works like 217 * this: 218 * 219 * 1) If there is a preferred foreground service, that service wins 220 * 2) Else, if there is a preferred payment service, that service wins 221 * 3) Else, if there is no winner, and all conflicting services will be 222 * in the list of resolved services. 223 */ 224 AidResolveInfo resolveAidConflictLocked(Collection<ServiceAidInfo> conflictingServices, 225 boolean makeSingleServiceDefault) { 226 if (conflictingServices == null || conflictingServices.size() == 0) { 227 Log.e(TAG, "resolveAidConflict: No services passed in."); 228 return null; 229 } 230 AidResolveInfo resolveInfo = new AidResolveInfo(); 231 resolveInfo.category = CardEmulation.CATEGORY_OTHER; 232 233 ApduServiceInfo matchedForeground = null; 234 ApduServiceInfo matchedPayment = null; 235 for (ServiceAidInfo serviceAidInfo : conflictingServices) { 236 boolean serviceClaimsPaymentAid = 237 CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category); 238 if (serviceAidInfo.service.getComponent().equals(mPreferredForegroundService)) { 239 resolveInfo.services.add(serviceAidInfo.service); 240 if (serviceClaimsPaymentAid) { 241 resolveInfo.category = CardEmulation.CATEGORY_PAYMENT; 242 } 243 matchedForeground = serviceAidInfo.service; 244 } else if (serviceAidInfo.service.getComponent().equals(mPreferredPaymentService) && 245 serviceClaimsPaymentAid) { 246 resolveInfo.services.add(serviceAidInfo.service); 247 resolveInfo.category = CardEmulation.CATEGORY_PAYMENT; 248 matchedPayment = serviceAidInfo.service; 249 } else { 250 if (serviceClaimsPaymentAid) { 251 // If this service claims it's a payment AID, don't route it, 252 // because it's not the default. Otherwise, add it to the list 253 // but not as default. 254 if (DBG) Log.d(TAG, "resolveAidLocked: (Ignoring handling service " + 255 serviceAidInfo.service.getComponent() + 256 " because it's not the payment default.)"); 257 } else { 258 resolveInfo.services.add(serviceAidInfo.service); 259 } 260 } 261 } 262 if (matchedForeground != null) { 263 // 1st priority: if the foreground app prefers a service, 264 // and that service asks for the AID, that service gets it 265 if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to foreground preferred " + 266 matchedForeground); 267 resolveInfo.defaultService = matchedForeground; 268 } else if (matchedPayment != null) { 269 // 2nd priority: if there is a preferred payment service, 270 // and that service claims this as a payment AID, that service gets it 271 if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to payment default " + 272 "default " + matchedPayment); 273 resolveInfo.defaultService = matchedPayment; 274 } else { 275 if (resolveInfo.services.size() == 1 && makeSingleServiceDefault) { 276 if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: making single handling service " + 277 resolveInfo.services.get(0).getComponent() + " default."); 278 resolveInfo.defaultService = resolveInfo.services.get(0); 279 } else { 280 // Nothing to do, all services already in list 281 if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to all matching services"); 282 } 283 } 284 return resolveInfo; 285 } 286 287 class DefaultServiceInfo { 288 ServiceAidInfo paymentDefault; 289 ServiceAidInfo foregroundDefault; 290 } 291 292 DefaultServiceInfo findDefaultServices(ArrayList<ServiceAidInfo> serviceAidInfos) { 293 DefaultServiceInfo defaultServiceInfo = new DefaultServiceInfo(); 294 295 for (ServiceAidInfo serviceAidInfo : serviceAidInfos) { 296 boolean serviceClaimsPaymentAid = 297 CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category); 298 if (serviceAidInfo.service.getComponent().equals(mPreferredForegroundService)) { 299 defaultServiceInfo.foregroundDefault = serviceAidInfo; 300 } else if (serviceAidInfo.service.getComponent().equals(mPreferredPaymentService) && 301 serviceClaimsPaymentAid) { 302 defaultServiceInfo.paymentDefault = serviceAidInfo; 303 } 304 } 305 return defaultServiceInfo; 306 } 307 308 AidResolveInfo resolvePrefixAidConflictLocked(ArrayList<ServiceAidInfo> prefixServices, 309 ArrayList<ServiceAidInfo> conflictingServices) { 310 // Find defaults among the prefix services themselves 311 DefaultServiceInfo prefixDefaultInfo = findDefaultServices(prefixServices); 312 313 // Find any defaults among the children 314 DefaultServiceInfo conflictingDefaultInfo = findDefaultServices(conflictingServices); 315 316 // Three conditions under which the prefix root AID gets to be the default 317 // 1. A service registering the prefix root AID is the current foreground preferred 318 // 2. A service registering the prefix root AID is the current tap & pay default AND 319 // no child is the current foreground preferred 320 // 3. There is only one service for the prefix root AID, and there are no children 321 if (prefixDefaultInfo.foregroundDefault != null) { 322 if (DBG) Log.d(TAG, "Prefix AID service " + 323 prefixDefaultInfo.foregroundDefault.service.getComponent() + " has foreground" + 324 " preference, ignoring conflicting AIDs."); 325 // Foreground default trumps any conflicting services, treat as normal AID conflict 326 // and ignore children 327 return resolveAidConflictLocked(prefixServices, true); 328 } else if (prefixDefaultInfo.paymentDefault != null) { 329 // Check if any of the conflicting services is foreground default 330 if (conflictingDefaultInfo.foregroundDefault != null) { 331 // Conflicting AID registration is in foreground, trumps prefix tap&pay default 332 if (DBG) Log.d(TAG, "One of the conflicting AID registrations is foreground " + 333 "preferred, ignoring prefix."); 334 return EMPTY_RESOLVE_INFO; 335 } else { 336 // Prefix service is tap&pay default, treat as normal AID conflict for just prefix 337 if (DBG) Log.d(TAG, "Prefix AID service " + 338 prefixDefaultInfo.paymentDefault.service.getComponent() + " is payment" + 339 " default, ignoring conflicting AIDs."); 340 return resolveAidConflictLocked(prefixServices, true); 341 } 342 } else { 343 if (conflictingDefaultInfo.foregroundDefault != null || 344 conflictingDefaultInfo.paymentDefault != null) { 345 if (DBG) Log.d(TAG, "One of the conflicting AID registrations is either payment " + 346 "default or foreground preferred, ignoring prefix."); 347 return EMPTY_RESOLVE_INFO; 348 } else { 349 // No children that are preferred; add all services of the root 350 // make single service default if no children are present 351 if (DBG) Log.d(TAG, "No service has preference, adding all."); 352 return resolveAidConflictLocked(prefixServices, conflictingServices.isEmpty()); 353 } 354 } 355 } 356 357 void generateServiceMapLocked(List<ApduServiceInfo> services) { 358 // Easiest is to just build the entire tree again 359 mAidServices.clear(); 360 for (ApduServiceInfo service : services) { 361 if (DBG) Log.d(TAG, "generateServiceMap component: " + service.getComponent()); 362 for (String aid : service.getAids()) { 363 if (!CardEmulation.isValidAid(aid)) { 364 Log.e(TAG, "Aid " + aid + " is not valid."); 365 continue; 366 } 367 if (aid.endsWith("*") && !supportsAidPrefixRegistration()) { 368 Log.e(TAG, "Prefix AID " + aid + " ignored on device that doesn't support it."); 369 continue; 370 } 371 ServiceAidInfo serviceAidInfo = new ServiceAidInfo(); 372 serviceAidInfo.aid = aid.toUpperCase(); 373 serviceAidInfo.service = service; 374 serviceAidInfo.category = service.getCategoryForAid(aid); 375 376 if (mAidServices.containsKey(serviceAidInfo.aid)) { 377 final ArrayList<ServiceAidInfo> serviceAidInfos = 378 mAidServices.get(serviceAidInfo.aid); 379 serviceAidInfos.add(serviceAidInfo); 380 } else { 381 final ArrayList<ServiceAidInfo> serviceAidInfos = 382 new ArrayList<ServiceAidInfo>(); 383 serviceAidInfos.add(serviceAidInfo); 384 mAidServices.put(serviceAidInfo.aid, serviceAidInfos); 385 } 386 } 387 } 388 } 389 390 static boolean isPrefix(String aid) { 391 return aid.endsWith("*"); 392 } 393 394 final class PrefixConflicts { 395 NavigableMap<String, ArrayList<ServiceAidInfo>> conflictMap; 396 final ArrayList<ServiceAidInfo> services = new ArrayList<ServiceAidInfo>(); 397 final HashSet<String> aids = new HashSet<String>(); 398 } 399 400 PrefixConflicts findConflictsForPrefixLocked(String prefixAid) { 401 PrefixConflicts prefixConflicts = new PrefixConflicts(); 402 String plainAid = prefixAid.substring(0, prefixAid.length() - 1); // Cut off "*" 403 String lastAidWithPrefix = String.format("%-32s", plainAid).replace(' ', 'F'); 404 if (DBG) Log.d(TAG, "Finding AIDs in range [" + plainAid + " - " + 405 lastAidWithPrefix + "]"); 406 prefixConflicts.conflictMap = 407 mAidServices.subMap(plainAid, true, lastAidWithPrefix, true); 408 for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry : 409 prefixConflicts.conflictMap.entrySet()) { 410 if (!entry.getKey().equalsIgnoreCase(prefixAid)) { 411 if (DBG) 412 Log.d(TAG, "AID " + entry.getKey() + " conflicts with prefix; " + 413 " adding handling services for conflict resolution."); 414 prefixConflicts.services.addAll(entry.getValue()); 415 prefixConflicts.aids.add(entry.getKey()); 416 } 417 } 418 return prefixConflicts; 419 } 420 421 void generateAidCacheLocked() { 422 mAidCache.clear(); 423 // Get all exact and prefix AIDs in an ordered list 424 PriorityQueue<String> aidsToResolve = new PriorityQueue<String>(mAidServices.keySet()); 425 426 while (!aidsToResolve.isEmpty()) { 427 final ArrayList<String> resolvedAids = new ArrayList<String>(); 428 429 String aidToResolve = aidsToResolve.peek(); 430 // Because of the lexicographical ordering, all following AIDs either start with the 431 // same bytes and are longer, or start with different bytes. 432 433 // A special case is if another service registered the same AID as a prefix, in 434 // which case we want to start with that AID, since it conflicts with this one 435 if (aidsToResolve.contains(aidToResolve + "*")) { 436 aidToResolve = aidToResolve + "*"; 437 } 438 if (DBG) Log.d(TAG, "generateAidCacheLocked: starting with aid " + aidToResolve); 439 440 if (isPrefix(aidToResolve)) { 441 // This AID itself is a prefix; let's consider this prefix as the "root", 442 // and all conflicting AIDs as its children. 443 // For example, if "A000000003*" is the prefix root, 444 // "A000000003", "A00000000301*", "A0000000030102" are all conflicting children AIDs 445 final ArrayList<ServiceAidInfo> prefixServices = new ArrayList<ServiceAidInfo>( 446 mAidServices.get(aidToResolve)); 447 448 // Find all conflicting children services 449 PrefixConflicts prefixConflicts = findConflictsForPrefixLocked(aidToResolve); 450 451 // Resolve conflicts 452 AidResolveInfo resolveInfo = resolvePrefixAidConflictLocked(prefixServices, 453 prefixConflicts.services); 454 mAidCache.put(aidToResolve, resolveInfo); 455 resolvedAids.add(aidToResolve); 456 if (resolveInfo.defaultService != null) { 457 // This prefix is the default; therefore, AIDs of all conflicting children 458 // will no longer be evaluated. 459 resolvedAids.addAll(prefixConflicts.aids); 460 } else if (resolveInfo.services.size() > 0) { 461 // This means we don't have a default for this prefix and all its 462 // conflicting children. So, for all conflicting AIDs, just add 463 // all handling services without setting a default 464 boolean foundChildService = false; 465 for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry : 466 prefixConflicts.conflictMap.entrySet()) { 467 if (!entry.getKey().equalsIgnoreCase(aidToResolve)) { 468 if (DBG) 469 Log.d(TAG, "AID " + entry.getKey() + " shared with prefix; " + 470 " adding all handling services."); 471 AidResolveInfo childResolveInfo = resolveAidConflictLocked( 472 entry.getValue(), false); 473 // Special case: in this case all children AIDs must be routed to the 474 // host, so we can ask the user which service is preferred. 475 // Since these are all "children" of the prefix, they don't need 476 // to be routed, since the prefix will already get routed to the host 477 childResolveInfo.mustRoute = false; 478 mAidCache.put(entry.getKey(),childResolveInfo); 479 resolvedAids.add(entry.getKey()); 480 foundChildService |= !childResolveInfo.services.isEmpty(); 481 } 482 } 483 // Special case: if in the end we didn't add any children services, 484 // and the prefix has only one service, make that default 485 if (!foundChildService && resolveInfo.services.size() == 1) { 486 resolveInfo.defaultService = resolveInfo.services.get(0); 487 } 488 } else { 489 // This prefix is not handled at all; we will evaluate 490 // the children separately in next passes. 491 } 492 } else { 493 // Exact AID and no other conflicting AID registrations present 494 // This is true because aidsToResolve is lexicographically ordered, and 495 // so by necessity all other AIDs are different than this AID or longer. 496 if (DBG) Log.d(TAG, "Exact AID, resolving."); 497 final ArrayList<ServiceAidInfo> conflictingServiceInfos = 498 new ArrayList<ServiceAidInfo>(mAidServices.get(aidToResolve)); 499 mAidCache.put(aidToResolve, resolveAidConflictLocked(conflictingServiceInfos, true)); 500 resolvedAids.add(aidToResolve); 501 } 502 503 // Remove the AIDs we resolved from the list of AIDs to resolve 504 if (DBG) Log.d(TAG, "AIDs: " + resolvedAids + " were resolved."); 505 aidsToResolve.removeAll(resolvedAids); 506 resolvedAids.clear(); 507 } 508 509 updateRoutingLocked(); 510 } 511 512 void updateRoutingLocked() { 513 if (!mNfcEnabled) { 514 if (DBG) Log.d(TAG, "Not updating routing table because NFC is off."); 515 return; 516 } 517 final HashMap<String, Boolean> routingEntries = Maps.newHashMap(); 518 // For each AID, find interested services 519 for (Map.Entry<String, AidResolveInfo> aidEntry: 520 mAidCache.entrySet()) { 521 String aid = aidEntry.getKey(); 522 AidResolveInfo resolveInfo = aidEntry.getValue(); 523 if (!resolveInfo.mustRoute) { 524 if (DBG) Log.d(TAG, "Not routing AID " + aid + " on request."); 525 continue; 526 } 527 if (resolveInfo.services.size() == 0) { 528 // No interested services 529 } else if (resolveInfo.defaultService != null) { 530 // There is a default service set, route to where that service resides - 531 // either on the host (HCE) or on an SE. 532 routingEntries.put(aid, resolveInfo.defaultService.isOnHost()); 533 } else if (resolveInfo.services.size() == 1) { 534 // Only one service, but not the default, must route to host 535 // to ask the user to choose one. 536 routingEntries.put(aid, true); 537 } else if (resolveInfo.services.size() > 1) { 538 // Multiple services, need to route to host to ask 539 routingEntries.put(aid, true); 540 } 541 } 542 mRoutingManager.configureRouting(routingEntries); 543 } 544 545 public void onServicesUpdated(int userId, List<ApduServiceInfo> services) { 546 if (DBG) Log.d(TAG, "onServicesUpdated"); 547 synchronized (mLock) { 548 if (ActivityManager.getCurrentUser() == userId) { 549 // Rebuild our internal data-structures 550 generateServiceMapLocked(services); 551 generateAidCacheLocked(); 552 } else { 553 if (DBG) Log.d(TAG, "Ignoring update because it's not for the current user."); 554 } 555 } 556 } 557 558 public void onPreferredPaymentServiceChanged(ComponentName service) { 559 if (DBG) Log.d(TAG, "Preferred payment service changed."); 560 synchronized (mLock) { 561 mPreferredPaymentService = service; 562 generateAidCacheLocked(); 563 } 564 } 565 566 public void onPreferredForegroundServiceChanged(ComponentName service) { 567 if (DBG) Log.d(TAG, "Preferred foreground service changed."); 568 synchronized (mLock) { 569 mPreferredForegroundService = service; 570 generateAidCacheLocked(); 571 } 572 } 573 574 public void onNfcDisabled() { 575 synchronized (mLock) { 576 mNfcEnabled = false; 577 } 578 mRoutingManager.onNfccRoutingTableCleared(); 579 } 580 581 public void onNfcEnabled() { 582 synchronized (mLock) { 583 mNfcEnabled = true; 584 updateRoutingLocked(); 585 } 586 } 587 588 String dumpEntry(Map.Entry<String, AidResolveInfo> entry) { 589 StringBuilder sb = new StringBuilder(); 590 String category = entry.getValue().category; 591 ApduServiceInfo defaultServiceInfo = entry.getValue().defaultService; 592 sb.append(" \"" + entry.getKey() + "\" (category: " + category + ")\n"); 593 ComponentName defaultComponent = defaultServiceInfo != null ? 594 defaultServiceInfo.getComponent() : null; 595 596 for (ApduServiceInfo serviceInfo : entry.getValue().services) { 597 sb.append(" "); 598 if (serviceInfo.getComponent().equals(defaultComponent)) { 599 sb.append("*DEFAULT* "); 600 } 601 sb.append(serviceInfo.getComponent() + 602 " (Description: " + serviceInfo.getDescription() + ")\n"); 603 } 604 return sb.toString(); 605 } 606 607 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 608 pw.println(" AID cache entries: "); 609 for (Map.Entry<String, AidResolveInfo> entry : mAidCache.entrySet()) { 610 pw.println(dumpEntry(entry)); 611 } 612 pw.println(" Service preferred by foreground app: " + mPreferredForegroundService); 613 pw.println(" Preferred payment service: " + mPreferredPaymentService); 614 pw.println(""); 615 mRoutingManager.dump(fd, pw, args); 616 pw.println(""); 617 } 618 } 619