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 17package com.android.nfc.cardemulation; 18 19import android.app.ActivityManager; 20import android.content.ComponentName; 21import android.content.Context; 22import android.nfc.cardemulation.ApduServiceInfo; 23import android.nfc.cardemulation.CardEmulation; 24import android.util.Log; 25 26import com.google.android.collect.Maps; 27 28import java.io.FileDescriptor; 29import java.io.PrintWriter; 30import java.util.ArrayList; 31import java.util.Collection; 32import java.util.HashMap; 33import java.util.HashSet; 34import java.util.List; 35import java.util.Map; 36import java.util.NavigableMap; 37import java.util.PriorityQueue; 38import java.util.TreeMap; 39 40public 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 " + entry.getKey() + " 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 List<String> prefixAids = service.getPrefixAids(); 363 for (String aid : service.getAids()) { 364 if (!CardEmulation.isValidAid(aid)) { 365 Log.e(TAG, "Aid " + aid + " is not valid."); 366 continue; 367 } 368 if (aid.endsWith("*") && !supportsAidPrefixRegistration()) { 369 Log.e(TAG, "Prefix AID " + aid + " ignored on device that doesn't support it."); 370 continue; 371 } else if (supportsAidPrefixRegistration() && prefixAids.size() > 0 && !isPrefix(aid)) { 372 // Check if we already have an overlapping prefix registered for this AID 373 boolean foundPrefix = false; 374 for (String prefixAid : prefixAids) { 375 String prefix = prefixAid.substring(0, prefixAid.length() - 1); 376 if (aid.startsWith(prefix)) { 377 Log.e(TAG, "Ignoring exact AID " + aid + " because prefix AID " + prefixAid + 378 " is already registered"); 379 foundPrefix = true; 380 break; 381 } 382 } 383 if (foundPrefix) { 384 continue; 385 } 386 } 387 ServiceAidInfo serviceAidInfo = new ServiceAidInfo(); 388 serviceAidInfo.aid = aid.toUpperCase(); 389 serviceAidInfo.service = service; 390 serviceAidInfo.category = service.getCategoryForAid(aid); 391 392 if (mAidServices.containsKey(serviceAidInfo.aid)) { 393 final ArrayList<ServiceAidInfo> serviceAidInfos = 394 mAidServices.get(serviceAidInfo.aid); 395 serviceAidInfos.add(serviceAidInfo); 396 } else { 397 final ArrayList<ServiceAidInfo> serviceAidInfos = 398 new ArrayList<ServiceAidInfo>(); 399 serviceAidInfos.add(serviceAidInfo); 400 mAidServices.put(serviceAidInfo.aid, serviceAidInfos); 401 } 402 } 403 } 404 } 405 406 static boolean isPrefix(String aid) { 407 return aid.endsWith("*"); 408 } 409 410 final class PrefixConflicts { 411 NavigableMap<String, ArrayList<ServiceAidInfo>> conflictMap; 412 final ArrayList<ServiceAidInfo> services = new ArrayList<ServiceAidInfo>(); 413 final HashSet<String> aids = new HashSet<String>(); 414 } 415 416 PrefixConflicts findConflictsForPrefixLocked(String prefixAid) { 417 PrefixConflicts prefixConflicts = new PrefixConflicts(); 418 String plainAid = prefixAid.substring(0, prefixAid.length() - 1); // Cut off "*" 419 String lastAidWithPrefix = String.format("%-32s", plainAid).replace(' ', 'F'); 420 if (DBG) Log.d(TAG, "Finding AIDs in range [" + plainAid + " - " + 421 lastAidWithPrefix + "]"); 422 prefixConflicts.conflictMap = 423 mAidServices.subMap(plainAid, true, lastAidWithPrefix, true); 424 for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry : 425 prefixConflicts.conflictMap.entrySet()) { 426 if (!entry.getKey().equalsIgnoreCase(prefixAid)) { 427 if (DBG) 428 Log.d(TAG, "AID " + entry.getKey() + " conflicts with prefix; " + 429 " adding handling services for conflict resolution."); 430 prefixConflicts.services.addAll(entry.getValue()); 431 prefixConflicts.aids.add(entry.getKey()); 432 } 433 } 434 return prefixConflicts; 435 } 436 437 void generateAidCacheLocked() { 438 mAidCache.clear(); 439 // Get all exact and prefix AIDs in an ordered list 440 PriorityQueue<String> aidsToResolve = new PriorityQueue<String>(mAidServices.keySet()); 441 442 while (!aidsToResolve.isEmpty()) { 443 final ArrayList<String> resolvedAids = new ArrayList<String>(); 444 445 String aidToResolve = aidsToResolve.peek(); 446 // Because of the lexicographical ordering, all following AIDs either start with the 447 // same bytes and are longer, or start with different bytes. 448 449 // A special case is if another service registered the same AID as a prefix, in 450 // which case we want to start with that AID, since it conflicts with this one 451 if (aidsToResolve.contains(aidToResolve + "*")) { 452 aidToResolve = aidToResolve + "*"; 453 } 454 if (DBG) Log.d(TAG, "generateAidCacheLocked: starting with aid " + aidToResolve); 455 456 if (isPrefix(aidToResolve)) { 457 // This AID itself is a prefix; let's consider this prefix as the "root", 458 // and all conflicting AIDs as its children. 459 // For example, if "A000000003*" is the prefix root, 460 // "A000000003", "A00000000301*", "A0000000030102" are all conflicting children AIDs 461 final ArrayList<ServiceAidInfo> prefixServices = new ArrayList<ServiceAidInfo>( 462 mAidServices.get(aidToResolve)); 463 464 // Find all conflicting children services 465 PrefixConflicts prefixConflicts = findConflictsForPrefixLocked(aidToResolve); 466 467 // Resolve conflicts 468 AidResolveInfo resolveInfo = resolvePrefixAidConflictLocked(prefixServices, 469 prefixConflicts.services); 470 mAidCache.put(aidToResolve, resolveInfo); 471 resolvedAids.add(aidToResolve); 472 if (resolveInfo.defaultService != null) { 473 // This prefix is the default; therefore, AIDs of all conflicting children 474 // will no longer be evaluated. 475 resolvedAids.addAll(prefixConflicts.aids); 476 } else if (resolveInfo.services.size() > 0) { 477 // This means we don't have a default for this prefix and all its 478 // conflicting children. So, for all conflicting AIDs, just add 479 // all handling services without setting a default 480 boolean foundChildService = false; 481 for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry : 482 prefixConflicts.conflictMap.entrySet()) { 483 if (!entry.getKey().equalsIgnoreCase(aidToResolve)) { 484 if (DBG) 485 Log.d(TAG, "AID " + entry.getKey() + " shared with prefix; " + 486 " adding all handling services."); 487 AidResolveInfo childResolveInfo = resolveAidConflictLocked( 488 entry.getValue(), false); 489 // Special case: in this case all children AIDs must be routed to the 490 // host, so we can ask the user which service is preferred. 491 // Since these are all "children" of the prefix, they don't need 492 // to be routed, since the prefix will already get routed to the host 493 childResolveInfo.mustRoute = false; 494 mAidCache.put(entry.getKey(),childResolveInfo); 495 resolvedAids.add(entry.getKey()); 496 foundChildService |= !childResolveInfo.services.isEmpty(); 497 } 498 } 499 // Special case: if in the end we didn't add any children services, 500 // and the prefix has only one service, make that default 501 if (!foundChildService && resolveInfo.services.size() == 1) { 502 resolveInfo.defaultService = resolveInfo.services.get(0); 503 } 504 } else { 505 // This prefix is not handled at all; we will evaluate 506 // the children separately in next passes. 507 } 508 } else { 509 // Exact AID and no other conflicting AID registrations present 510 // This is true because aidsToResolve is lexicographically ordered, and 511 // so by necessity all other AIDs are different than this AID or longer. 512 if (DBG) Log.d(TAG, "Exact AID, resolving."); 513 final ArrayList<ServiceAidInfo> conflictingServiceInfos = 514 new ArrayList<ServiceAidInfo>(mAidServices.get(aidToResolve)); 515 mAidCache.put(aidToResolve, resolveAidConflictLocked(conflictingServiceInfos, true)); 516 resolvedAids.add(aidToResolve); 517 } 518 519 // Remove the AIDs we resolved from the list of AIDs to resolve 520 if (DBG) Log.d(TAG, "AIDs: " + resolvedAids + " were resolved."); 521 aidsToResolve.removeAll(resolvedAids); 522 resolvedAids.clear(); 523 } 524 525 updateRoutingLocked(); 526 } 527 528 void updateRoutingLocked() { 529 if (!mNfcEnabled) { 530 if (DBG) Log.d(TAG, "Not updating routing table because NFC is off."); 531 return; 532 } 533 final HashMap<String, Boolean> routingEntries = Maps.newHashMap(); 534 // For each AID, find interested services 535 for (Map.Entry<String, AidResolveInfo> aidEntry: 536 mAidCache.entrySet()) { 537 String aid = aidEntry.getKey(); 538 AidResolveInfo resolveInfo = aidEntry.getValue(); 539 if (!resolveInfo.mustRoute) { 540 if (DBG) Log.d(TAG, "Not routing AID " + aid + " on request."); 541 continue; 542 } 543 if (resolveInfo.services.size() == 0) { 544 // No interested services 545 } else if (resolveInfo.defaultService != null) { 546 // There is a default service set, route to where that service resides - 547 // either on the host (HCE) or on an SE. 548 routingEntries.put(aid, resolveInfo.defaultService.isOnHost()); 549 } else if (resolveInfo.services.size() == 1) { 550 // Only one service, but not the default, must route to host 551 // to ask the user to choose one. 552 routingEntries.put(aid, true); 553 } else if (resolveInfo.services.size() > 1) { 554 // Multiple services, need to route to host to ask 555 routingEntries.put(aid, true); 556 } 557 } 558 mRoutingManager.configureRouting(routingEntries); 559 } 560 561 public void onServicesUpdated(int userId, List<ApduServiceInfo> services) { 562 if (DBG) Log.d(TAG, "onServicesUpdated"); 563 synchronized (mLock) { 564 if (ActivityManager.getCurrentUser() == userId) { 565 // Rebuild our internal data-structures 566 generateServiceMapLocked(services); 567 generateAidCacheLocked(); 568 } else { 569 if (DBG) Log.d(TAG, "Ignoring update because it's not for the current user."); 570 } 571 } 572 } 573 574 public void onPreferredPaymentServiceChanged(ComponentName service) { 575 if (DBG) Log.d(TAG, "Preferred payment service changed."); 576 synchronized (mLock) { 577 mPreferredPaymentService = service; 578 generateAidCacheLocked(); 579 } 580 } 581 582 public void onPreferredForegroundServiceChanged(ComponentName service) { 583 if (DBG) Log.d(TAG, "Preferred foreground service changed."); 584 synchronized (mLock) { 585 mPreferredForegroundService = service; 586 generateAidCacheLocked(); 587 } 588 } 589 590 public void onNfcDisabled() { 591 synchronized (mLock) { 592 mNfcEnabled = false; 593 } 594 mRoutingManager.onNfccRoutingTableCleared(); 595 } 596 597 public void onNfcEnabled() { 598 synchronized (mLock) { 599 mNfcEnabled = true; 600 updateRoutingLocked(); 601 } 602 } 603 604 String dumpEntry(Map.Entry<String, AidResolveInfo> entry) { 605 StringBuilder sb = new StringBuilder(); 606 String category = entry.getValue().category; 607 ApduServiceInfo defaultServiceInfo = entry.getValue().defaultService; 608 sb.append(" \"" + entry.getKey() + "\" (category: " + category + ")\n"); 609 ComponentName defaultComponent = defaultServiceInfo != null ? 610 defaultServiceInfo.getComponent() : null; 611 612 for (ApduServiceInfo serviceInfo : entry.getValue().services) { 613 sb.append(" "); 614 if (serviceInfo.getComponent().equals(defaultComponent)) { 615 sb.append("*DEFAULT* "); 616 } 617 sb.append(serviceInfo.getComponent() + 618 " (Description: " + serviceInfo.getDescription() + ")\n"); 619 } 620 return sb.toString(); 621 } 622 623 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 624 pw.println(" AID cache entries: "); 625 for (Map.Entry<String, AidResolveInfo> entry : mAidCache.entrySet()) { 626 pw.println(dumpEntry(entry)); 627 } 628 pw.println(" Service preferred by foreground app: " + mPreferredForegroundService); 629 pw.println(" Preferred payment service: " + mPreferredPaymentService); 630 pw.println(""); 631 mRoutingManager.dump(fd, pw, args); 632 pw.println(""); 633 } 634} 635