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.annotation.NonNull; 20 import android.annotation.SuppressLint; 21 import android.annotation.TargetApi; 22 import android.app.ActivityManager; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.pm.PackageManager; 26 import android.nfc.ComponentNameAndUser; 27 import android.nfc.Flags; 28 import android.nfc.INfcOemExtensionCallback; 29 import android.nfc.cardemulation.ApduServiceInfo; 30 import android.nfc.cardemulation.CardEmulation; 31 import android.nfc.cardemulation.Utils; 32 import android.os.RemoteException; 33 import android.os.UserHandle; 34 import android.os.UserManager; 35 import android.sysprop.NfcProperties; 36 import android.util.Log; 37 import android.util.proto.ProtoOutputStream; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.nfc.NfcService; 41 import com.android.nfc.cardemulation.util.TelephonyUtils; 42 43 import java.io.FileDescriptor; 44 import java.io.PrintWriter; 45 import java.util.ArrayList; 46 import java.util.Collection; 47 import java.util.Collections; 48 import java.util.HashMap; 49 import java.util.HashSet; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.NavigableMap; 53 import java.util.PriorityQueue; 54 import java.util.Set; 55 import java.util.TreeMap; 56 import java.util.stream.Collectors; 57 58 public class RegisteredAidCache { 59 static final String TAG = "RegisteredAidCache"; 60 private INfcOemExtensionCallback mNfcOemExtensionCallback; 61 62 static final boolean DBG = NfcProperties.debug_enabled().orElse(true); 63 private static final boolean VDBG = false; // turn on for local testing. 64 65 static final int AID_ROUTE_QUAL_SUBSET = 0x20; 66 static final int AID_ROUTE_QUAL_PREFIX = 0x10; 67 68 static final int POWER_STATE_SWITCH_ON = 0x1; 69 static final int POWER_STATE_SWITCH_OFF = 0x2; 70 static final int POWER_STATE_BATTERY_OFF = 0x4; 71 static final int POWER_STATE_SCREEN_OFF_UNLOCKED = 0x8; 72 static final int POWER_STATE_SCREEN_ON_LOCKED = 0x10; 73 static final int POWER_STATE_SCREEN_OFF_LOCKED = 0x20; 74 static final int POWER_STATE_ALL = POWER_STATE_SWITCH_ON | POWER_STATE_SWITCH_OFF 75 | POWER_STATE_BATTERY_OFF | POWER_STATE_SCREEN_OFF_UNLOCKED 76 | POWER_STATE_SCREEN_ON_LOCKED | POWER_STATE_SCREEN_OFF_LOCKED; 77 static final int POWER_STATE_ALL_NCI_VERSION_1_0 = POWER_STATE_SWITCH_ON 78 | POWER_STATE_SWITCH_OFF 79 | POWER_STATE_BATTERY_OFF; 80 81 final Map<Integer, List<ApduServiceInfo>> mUserApduServiceInfo = 82 new HashMap<Integer, List<ApduServiceInfo>>(); 83 // mAidServices maps AIDs to services that have registered them. 84 // It's a TreeMap in order to be able to quickly select subsets 85 // of AIDs that conflict with each other. 86 final TreeMap<String, ArrayList<ServiceAidInfo>> mAidServices = 87 new TreeMap<String, ArrayList<ServiceAidInfo>>(); 88 89 // mAidCache is a lookup table for quickly mapping an exact or prefix or subset AID 90 // to one or more handling services. It differs from mAidServices in the sense that it 91 // has already accounted for defaults, and hence its return value 92 // is authoritative for the current set of services and defaults. 93 // It is only valid for the current user. 94 final TreeMap<String, AidResolveInfo> mAidCache = new TreeMap<String, AidResolveInfo>(); 95 96 // Represents a single AID registration of a service 97 final class ServiceAidInfo { 98 ApduServiceInfo service; 99 String aid; 100 String category; 101 102 @Override toString()103 public String toString() { 104 return "ServiceAidInfo{" + 105 "service=" + service.getComponent() + 106 ", aid='" + aid + '\'' + 107 ", category='" + category + '\'' + 108 '}'; 109 } 110 111 @Override equals(Object o)112 public boolean equals(Object o) { 113 if (this == o) return true; 114 if (o == null || getClass() != o.getClass()) return false; 115 116 ServiceAidInfo that = (ServiceAidInfo) o; 117 118 if (!aid.equals(that.aid)) return false; 119 if (!category.equals(that.category)) return false; 120 if (!service.equals(that.service)) return false; 121 122 return true; 123 } 124 125 @Override hashCode()126 public int hashCode() { 127 int result = service.hashCode(); 128 result = 31 * result + aid.hashCode(); 129 result = 31 * result + category.hashCode(); 130 return result; 131 } 132 } 133 134 // Represents a list of services, an optional default and a category that 135 // an AID was resolved to. 136 final class AidResolveInfo { 137 List<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>(); 138 ApduServiceInfo defaultService = null; 139 String category = null; 140 ResolvedPrefixConflictAid prefixInfo = null; 141 List<String> unCheckedOffHostSecureElement = new ArrayList<>(); 142 @Override toString()143 public String toString() { 144 return "AidResolveInfo{" 145 + "services=" + services 146 + ", defaultService=" + defaultService 147 + ", category='" + category 148 + '}'; 149 } 150 getCategory()151 String getCategory() { 152 return category; 153 } 154 } 155 156 final AidResolveInfo EMPTY_RESOLVE_INFO = new AidResolveInfo(); 157 158 final Context mContext; 159 160 final WalletRoleObserver mWalletRoleObserver; 161 final AidRoutingManager mRoutingManager; 162 163 final Object mLock = new Object(); 164 165 ComponentName mPreferredPaymentService; 166 int mUserIdPreferredPaymentService; 167 ComponentName mPreferredForegroundService; 168 int mUserIdPreferredForegroundService; 169 170 String mDefaultWalletHolderPackageName; 171 172 int mUserIdDefaultWalletHolder; 173 174 boolean mNfcEnabled = false; 175 boolean mSupportsPrefixes = false; 176 boolean mSupportsSubset = false; 177 boolean mRequiresScreenOnServiceExist = false; 178 179 Set<ApduServiceInfo> mAssociatedRoleServices = new HashSet<>(); 180 181 int mPreferredSimType = TelephonyUtils.SIM_TYPE_UNKNOWN; 182 RegisteredAidCache(Context context, WalletRoleObserver walletRoleObserver)183 public RegisteredAidCache(Context context, WalletRoleObserver walletRoleObserver) { 184 this(context, walletRoleObserver, new AidRoutingManager()); 185 } 186 187 @VisibleForTesting RegisteredAidCache(Context context, WalletRoleObserver walletRoleObserver, AidRoutingManager routingManager)188 public RegisteredAidCache(Context context, WalletRoleObserver walletRoleObserver, 189 AidRoutingManager routingManager) { 190 mContext = context; 191 mWalletRoleObserver = walletRoleObserver; 192 mRoutingManager = routingManager; 193 mPreferredPaymentService = null; 194 mUserIdPreferredPaymentService = -1; 195 mPreferredForegroundService = null; 196 mUserIdPreferredForegroundService = -1; 197 mSupportsPrefixes = mRoutingManager.supportsAidPrefixRouting(); 198 mSupportsSubset = mRoutingManager.supportsAidSubsetRouting(); 199 if (mSupportsPrefixes) { 200 if (DBG) Log.d(TAG, "RegisteredAidCache: Controller supports AID prefix routing"); 201 } 202 if (mSupportsSubset) { 203 if (DBG) Log.d(TAG, "RegisteredAidCache: Controller supports AID subset routing"); 204 } 205 } 206 resolveAid(String aid)207 public AidResolveInfo resolveAid(String aid) { 208 synchronized (mLock) { 209 if (DBG) Log.d(TAG, "resolveAid: resolving AID " + aid); 210 if (aid.length() < 10) { 211 Log.e(TAG, "resolveAid: AID selected with fewer than 5 bytes."); 212 return EMPTY_RESOLVE_INFO; 213 } 214 AidResolveInfo resolveInfo = new AidResolveInfo(); 215 if (mSupportsPrefixes || mSupportsSubset) { 216 // Our AID cache may contain prefixes/subset which also match this AID, 217 // so we must find all potential prefixes or suffixes and merge the ResolveInfo 218 // of those prefixes plus any exact match in a single result. 219 String shortestAidMatch = aid.substring(0, 10); // Minimum AID length is 5 bytes 220 String longestAidMatch = String.format("%-32s", aid).replace(' ', 'F'); 221 222 223 if (DBG) { 224 Log.d(TAG, "resolveAid: Finding AID registrations in range [" + shortestAidMatch 225 + " - " + longestAidMatch + "]"); 226 } 227 NavigableMap<String, AidResolveInfo> matchingAids = 228 mAidCache.subMap(shortestAidMatch, true, longestAidMatch, true); 229 230 resolveInfo.category = CardEmulation.CATEGORY_OTHER; 231 for (Map.Entry<String, AidResolveInfo> entry : matchingAids.entrySet()) { 232 boolean isPrefix = isPrefix(entry.getKey()); 233 boolean isSubset = isSubset(entry.getKey()); 234 String entryAid = (isPrefix || isSubset) ? entry.getKey().substring(0, 235 entry.getKey().length() - 1):entry.getKey(); // Cut off '*' if prefix 236 if (entryAid.equalsIgnoreCase(aid) || (isPrefix && aid.startsWith(entryAid)) 237 || (isSubset && entryAid.startsWith(aid))) { 238 if (DBG) Log.d(TAG, "resolveAid: AID " + entry.getKey() + " matches."); 239 AidResolveInfo entryResolveInfo = entry.getValue(); 240 if (entryResolveInfo.defaultService != null) { 241 if (resolveInfo.defaultService != null) { 242 // This shouldn't happen; for every prefix we have only one 243 // default service. 244 Log.e(TAG, "resolveAid: Different defaults for conflicting AIDs!"); 245 } 246 resolveInfo.defaultService = entryResolveInfo.defaultService; 247 resolveInfo.category = entryResolveInfo.category; 248 } 249 for (ApduServiceInfo serviceInfo : entryResolveInfo.services) { 250 if (!resolveInfo.services.contains(serviceInfo)) { 251 resolveInfo.services.add(serviceInfo); 252 } 253 } 254 } 255 } 256 } else { 257 resolveInfo = mAidCache.get(aid); 258 } 259 if (DBG) Log.d(TAG, "resolveAid: Resolved to: " + resolveInfo); 260 return resolveInfo; 261 } 262 } 263 supportsAidPrefixRegistration()264 public boolean supportsAidPrefixRegistration() { 265 return mSupportsPrefixes; 266 } 267 supportsAidSubsetRegistration()268 public boolean supportsAidSubsetRegistration() { 269 return mSupportsSubset; 270 } 271 isDefaultServiceForAid(int userId, ComponentName service, String aid)272 public boolean isDefaultServiceForAid(int userId, ComponentName service, String aid) { 273 AidResolveInfo resolveInfo = resolveAid(aid); 274 if (resolveInfo == null || resolveInfo.services == null || 275 resolveInfo.services.size() == 0) { 276 return false; 277 } 278 if (resolveInfo.defaultService != null) { 279 return service.equals(resolveInfo.defaultService.getComponent()); 280 } else if (resolveInfo.services.size() == 1) { 281 return service.equals(resolveInfo.services.get(0).getComponent()); 282 } else { 283 Log.d(TAG, "isDefaultServiceForAid: Not Default Service: " + service.getClassName()); 284 // More than one service, not the default 285 return false; 286 } 287 } 288 isRequiresScreenOnServiceExist()289 public boolean isRequiresScreenOnServiceExist() { 290 return mRequiresScreenOnServiceExist; 291 } 292 293 @TargetApi(35) resolvePollingLoopFilterConflict(List<ApduServiceInfo> conflictingServices)294 ApduServiceInfo resolvePollingLoopFilterConflict(List<ApduServiceInfo> conflictingServices) { 295 ApduServiceInfo matchedForeground = null; 296 List<ApduServiceInfo> roleHolderServices = new ArrayList<>(); 297 ApduServiceInfo matchedPayment = null; 298 for (ApduServiceInfo serviceInfo : conflictingServices) { 299 int userId = UserHandle.getUserHandleForUid(serviceInfo.getUid()) 300 .getIdentifier(); 301 ComponentName componentName = serviceInfo.getComponent(); 302 303 if (componentName.equals(mPreferredForegroundService) && 304 userId == mUserIdPreferredForegroundService) { 305 matchedForeground = serviceInfo; 306 } else if (mWalletRoleObserver.isWalletRoleFeatureEnabled()) { 307 if (isDefaultOrAssociatedWalletService(serviceInfo, userId)) { 308 roleHolderServices.add(serviceInfo); 309 } 310 } else if (componentName.equals(mPreferredPaymentService) && 311 userId == mUserIdPreferredPaymentService) { 312 matchedPayment = serviceInfo; 313 } 314 } 315 if (matchedForeground != null) { 316 return matchedForeground; 317 } 318 if (mWalletRoleObserver.isWalletRoleFeatureEnabled()) { 319 roleHolderServices.sort((o1, o2) -> 320 String.CASE_INSENSITIVE_ORDER.compare(o1.getComponent().toShortString(), 321 o2.getComponent().toShortString())); 322 return roleHolderServices.isEmpty() ? null : roleHolderServices.get(0); 323 } 324 return matchedPayment; 325 } 326 nonDefaultResolution(boolean serviceClaimsPaymentAid, ServiceAidInfo serviceAidInfo, AidResolveInfo resolveInfo)327 private static void nonDefaultResolution(boolean serviceClaimsPaymentAid, 328 ServiceAidInfo serviceAidInfo, AidResolveInfo resolveInfo) { 329 if (serviceClaimsPaymentAid) { 330 // If this service claims it's a payment AID, don't route it, 331 // because it's not the default. Otherwise, add it to the list 332 // but not as default. 333 if (VDBG) { 334 Log.d(TAG, 335 "nonDefaultResolution: (Ignoring handling service " 336 + serviceAidInfo.service.getComponent() 337 + " because it's not the payment default.)"); 338 } 339 } else { 340 if (serviceAidInfo.service.isCategoryOtherServiceEnabled()) { 341 if (VDBG) { 342 Log.d(TAG, "nonDefaultResolution: " + serviceAidInfo.service.getComponent() 343 + " is selected other service"); 344 } 345 resolveInfo.services.add(serviceAidInfo.service); 346 } else { 347 if (DBG) { 348 Log.d(TAG, "nonDefaultResolution: " + serviceAidInfo.service.getComponent() 349 + " is unselected other service"); 350 } 351 if (!serviceAidInfo.service.isOnHost()) { 352 String offHostName = serviceAidInfo.service.getOffHostSecureElement(); 353 if (offHostName != null && 354 !resolveInfo.unCheckedOffHostSecureElement.contains(offHostName)) { 355 if (DBG) { 356 Log.d(TAG, "nonDefaultResolution: add " + offHostName 357 + " to disabled offHosts"); 358 } 359 resolveInfo.unCheckedOffHostSecureElement.add(offHostName); 360 } 361 } 362 } 363 364 } 365 } 366 nonDefaultRouting(AidResolveInfo resolveInfo, boolean makeSingleServiceDefault)367 private static void nonDefaultRouting(AidResolveInfo resolveInfo, 368 boolean makeSingleServiceDefault) { 369 if (resolveInfo.services.size() == 1 && makeSingleServiceDefault) { 370 if (DBG) { 371 Log.d(TAG, "nonDefaultRouting: DECISION: making single handling service " 372 + resolveInfo.services.get(0).getComponent() + " default."); 373 } 374 resolveInfo.defaultService = resolveInfo.services.get(0); 375 } else { 376 // Nothing to do, all services already in list 377 if (DBG) { 378 Log.d(TAG, "nonDefaultRouting: DECISION: routing to all matching services"); 379 } 380 } 381 } 382 isDefaultOrAssociatedWalletService(ApduServiceInfo serviceInfo, int userId)383 boolean isDefaultOrAssociatedWalletService(ApduServiceInfo serviceInfo, int userId) { 384 synchronized (mLock) { 385 if (userId != mUserIdDefaultWalletHolder) { 386 return false; 387 } 388 389 if (serviceInfo.getComponent().getPackageName().equals( 390 mDefaultWalletHolderPackageName)) { 391 return true; 392 } 393 394 if (Flags.nfcAssociatedRoleServices()) { 395 for (ApduServiceInfo associatedService : mAssociatedRoleServices) { 396 if (associatedService.getComponent().equals(serviceInfo.getComponent())) { 397 return true; 398 } 399 } 400 } 401 402 return false; 403 } 404 } 405 isDefaultOrAssociatedWalletPackage(String packageName, int userId)406 boolean isDefaultOrAssociatedWalletPackage(String packageName, int userId) { 407 synchronized (mLock) { 408 if (userId != mUserIdDefaultWalletHolder) { 409 return false; 410 } 411 412 if (packageName.equals(mDefaultWalletHolderPackageName)) { 413 return true; 414 } 415 416 if (Flags.nfcAssociatedRoleServices()) { 417 for (ApduServiceInfo associatedService : mAssociatedRoleServices) { 418 if (associatedService.getComponent().getPackageName().equals(packageName)) { 419 return true; 420 } 421 } 422 } 423 424 return false; 425 } 426 } 427 428 /** 429 * Resolves a conflict between multiple services handling the same 430 * AIDs. Note that the AID itself is not an input to the decision 431 * process - the algorithm just looks at the competing services 432 * and what preferences the user has indicated. In short, it works like 433 * this: 434 * 435 * 1) If there is a preferred foreground service, that service wins 436 * 2) Else if there is a default wallet app, that app wins 437 * 3) Else, if there is a preferred payment service, that service wins 438 * 4) Else, if there is no winner, and all conflicting services will be 439 * in the list of resolved services. 440 */ resolveAidConflictLocked(Collection<ServiceAidInfo> conflictingServices, boolean makeSingleServiceDefault)441 AidResolveInfo resolveAidConflictLocked(Collection<ServiceAidInfo> conflictingServices, 442 boolean makeSingleServiceDefault) { 443 if (conflictingServices == null || conflictingServices.size() == 0) { 444 Log.e(TAG, "resolveAidConflictLocked: No services passed in."); 445 return null; 446 } 447 AidResolveInfo resolveInfo = new AidResolveInfo(); 448 resolveInfo.category = CardEmulation.CATEGORY_OTHER; 449 450 ApduServiceInfo matchedForeground = null; 451 ApduServiceInfo matchedPayment = null; 452 List<ApduServiceInfo> defaultWalletServices = new ArrayList<>(); 453 454 // [nfc_w_temp] Implement eSIM 455 List<ServiceAidInfo> filteredServices; 456 if (mPreferredSimType == TelephonyUtils.SIM_TYPE_UNKNOWN) { 457 if (DBG) { 458 Log.i(TAG, "resolveAidConflictLocked: Sim based service is removed " 459 + "due to unknown sim type"); 460 } 461 filteredServices = conflictingServices.stream().filter( 462 serviceAidInfo-> { 463 if (serviceAidInfo.service.isOnHost()) return true; 464 String offHost = serviceAidInfo.service.getOffHostSecureElement(); 465 if (offHost != null) { 466 return !offHost.startsWith(RoutingOptionManager.SE_PREFIX_SIM); 467 } 468 return false; 469 } 470 ).collect(Collectors.toList()); 471 } else { 472 filteredServices = conflictingServices.stream().collect(Collectors.toList()); 473 } 474 475 for (ServiceAidInfo serviceAidInfo : filteredServices) { 476 boolean serviceClaimsPaymentAid = 477 CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category); 478 int userId = UserHandle.getUserHandleForUid(serviceAidInfo.service.getUid()) 479 .getIdentifier(); 480 ComponentName componentName = serviceAidInfo.service.getComponent(); 481 482 if (componentName.equals(mPreferredForegroundService) && 483 userId == mUserIdPreferredForegroundService) { 484 if (VDBG) { 485 Log.d(TAG, "resolveAidConflictLocked: Prioritizing foreground services"); 486 } 487 resolveInfo.services.add(serviceAidInfo.service); 488 if (serviceClaimsPaymentAid) { 489 resolveInfo.category = CardEmulation.CATEGORY_PAYMENT; 490 } 491 matchedForeground = serviceAidInfo.service; 492 } else if (mWalletRoleObserver.isWalletRoleFeatureEnabled()) { 493 if (isDefaultOrAssociatedWalletService(serviceAidInfo.service, userId)) { 494 if (VDBG) { 495 Log.d(TAG, 496 "resolveAidConflictLocked: Prioritizing default wallet services"); 497 } 498 if (serviceClaimsPaymentAid || 499 serviceAidInfo.service.isCategoryOtherServiceEnabled()) { 500 resolveInfo.services.add(serviceAidInfo.service); 501 if (serviceClaimsPaymentAid) { 502 resolveInfo.category = CardEmulation.CATEGORY_PAYMENT; 503 } 504 defaultWalletServices.add(serviceAidInfo.service); 505 } else { 506 if (VDBG) { 507 Log.d(TAG, 508 "resolveAidConflictLocked: Service disabled in default wallet, " 509 + "resolving against other applications"); 510 } 511 nonDefaultResolution(serviceClaimsPaymentAid, serviceAidInfo, resolveInfo); 512 } 513 } else { 514 nonDefaultResolution(serviceClaimsPaymentAid, serviceAidInfo, resolveInfo); 515 } 516 } else { 517 if (componentName.equals(mPreferredPaymentService) 518 && userId == mUserIdPreferredPaymentService && serviceClaimsPaymentAid) { 519 if (DBG) { 520 Log.d(TAG, "resolveAidConflictLocked: Prioritizing dpp services"); 521 } 522 resolveInfo.services.add(serviceAidInfo.service); 523 resolveInfo.category = CardEmulation.CATEGORY_PAYMENT; 524 matchedPayment = serviceAidInfo.service; 525 } else { 526 nonDefaultResolution(serviceClaimsPaymentAid, serviceAidInfo, resolveInfo); 527 } 528 } 529 } 530 if (matchedForeground != null) { 531 // 1st priority: if the foreground app prefers a service, 532 // and that service asks for the AID, that service gets it 533 if (DBG) { 534 Log.d(TAG, "resolveAidConflictLocked: DECISION: routing to foreground preferred " 535 + matchedForeground); 536 } 537 resolveInfo.defaultService = matchedForeground; 538 539 // Wallet Role Holder and the PreferredPaymentService are mutually exclusive. If the wallet 540 // role feature is enabled, the matched payment check should not take place at all. 541 } else if (mWalletRoleObserver.isWalletRoleFeatureEnabled() && 542 !defaultWalletServices.isEmpty()) { 543 // 2nd priority: if there is a default wallet application with services that 544 // claim this AID, that application gets it. 545 if (DBG) { 546 Log.d(TAG, "resolveAidConflictLocked: DECISION: routing to default wallet " 547 + mDefaultWalletHolderPackageName); 548 } 549 // If the role holder has multiple services with the same AID type, then we select 550 // the first one. The services are sorted alphabetically based on their component 551 // names. 552 defaultWalletServices.sort((o1, o2) -> 553 String.CASE_INSENSITIVE_ORDER.compare(o1.getComponent().toShortString(), 554 o2.getComponent().toShortString())); 555 resolveInfo.defaultService = defaultWalletServices.get(0); 556 } else if (matchedPayment != null) { 557 // 3d priority: if there is a preferred payment service, 558 // and that service claims this as a payment AID, that service gets it 559 if (DBG) { 560 Log.d(TAG, "resolveAidConflictLocked: DECISION: routing to payment default " 561 + "default " + matchedPayment); 562 } 563 resolveInfo.defaultService = matchedPayment; 564 } else { 565 nonDefaultRouting(resolveInfo, makeSingleServiceDefault); 566 } 567 return resolveInfo; 568 } 569 570 class DefaultServiceInfo { 571 ServiceAidInfo paymentDefault; 572 ServiceAidInfo foregroundDefault; 573 List<ServiceAidInfo> walletDefaults = new ArrayList<>(); 574 } 575 findDefaultServices(ArrayList<ServiceAidInfo> serviceAidInfos)576 DefaultServiceInfo findDefaultServices(ArrayList<ServiceAidInfo> serviceAidInfos) { 577 DefaultServiceInfo defaultServiceInfo = new DefaultServiceInfo(); 578 579 for (ServiceAidInfo serviceAidInfo : serviceAidInfos) { 580 boolean serviceClaimsPaymentAid = 581 CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category); 582 int userId = UserHandle.getUserHandleForUid(serviceAidInfo.service.getUid()) 583 .getIdentifier(); 584 ComponentName componentName = serviceAidInfo.service.getComponent(); 585 586 if (componentName.equals(mPreferredForegroundService) && 587 userId == mUserIdPreferredForegroundService) { 588 defaultServiceInfo.foregroundDefault = serviceAidInfo; 589 } else if (mWalletRoleObserver.isWalletRoleFeatureEnabled()) { 590 if (isDefaultOrAssociatedWalletService(serviceAidInfo.service, userId)) { 591 defaultServiceInfo.walletDefaults.add(serviceAidInfo); 592 } 593 }else if (componentName.equals(mPreferredPaymentService) && 594 userId == mUserIdPreferredPaymentService && 595 serviceClaimsPaymentAid) { 596 defaultServiceInfo.paymentDefault = serviceAidInfo; 597 } 598 } 599 return defaultServiceInfo; 600 } 601 noChildrenAidsPreferred(ArrayList<ServiceAidInfo> aidServices, ArrayList<ServiceAidInfo> conflictingServices)602 private AidResolveInfo noChildrenAidsPreferred(ArrayList<ServiceAidInfo> aidServices, 603 ArrayList<ServiceAidInfo> conflictingServices) { 604 // No children that are preferred; add all services of the root 605 // make single service default if no children are present 606 if (DBG) Log.d(TAG, "noChildrenAidsPreferred: No service has preference, adding all"); 607 AidResolveInfo resolveinfo = 608 resolveAidConflictLocked(aidServices, conflictingServices.isEmpty()); 609 //If the AID is subsetAID check for conflicting prefix in all 610 //conflciting services and root services. 611 if (isSubset(aidServices.get(0).aid)) { 612 ArrayList<ApduServiceInfo> apduServiceList = new ArrayList<ApduServiceInfo>(); 613 for (ServiceAidInfo serviceInfo : conflictingServices) 614 apduServiceList.add(serviceInfo.service); 615 for (ServiceAidInfo serviceInfo : aidServices) 616 apduServiceList.add(serviceInfo.service); 617 resolveinfo.prefixInfo = findPrefixConflictForSubsetAid( 618 aidServices.get(0).aid, apduServiceList, false); 619 } 620 return resolveinfo; 621 } 622 resolveAidConflictLocked(ArrayList<ServiceAidInfo> aidServices, ArrayList<ServiceAidInfo> conflictingServices)623 AidResolveInfo resolveAidConflictLocked(ArrayList<ServiceAidInfo> aidServices, 624 ArrayList<ServiceAidInfo> conflictingServices) { 625 // Find defaults among the root AID services themselves 626 DefaultServiceInfo aidDefaultInfo = findDefaultServices(aidServices); 627 628 // Find any defaults among the children 629 DefaultServiceInfo conflictingDefaultInfo = findDefaultServices(conflictingServices); 630 AidResolveInfo resolveinfo; 631 // Three conditions under which the root AID gets to be the default 632 // 1. A service registering the root AID is the current foreground preferred 633 // 2. A service registering the root AID is the wallet role holder AND no child 634 // child is the current foreground preferred 635 // 3. A service registering the root AID is the current tap & pay default AND 636 // no child is the current foreground preferred 637 // 4. There is only one service for the root AID, and there are no children 638 if (aidDefaultInfo.foregroundDefault != null) { 639 if (DBG) { 640 Log.d(TAG, 641 "resolveAidConflictLocked: Prefix AID service " 642 + aidDefaultInfo.foregroundDefault.service.getComponent() 643 + " has foreground" + " preference, ignoring conflicting AIDs"); 644 } 645 // Foreground default trumps any conflicting services, treat as normal AID conflict 646 // and ignore children 647 resolveinfo = resolveAidConflictLocked(aidServices, true); 648 //If the AID is subsetAID check for prefix in same service. 649 if (isSubset(aidServices.get(0).aid)) { 650 resolveinfo.prefixInfo = findPrefixConflictForSubsetAid(aidServices.get(0).aid, 651 List.of(resolveinfo.defaultService), true); 652 } 653 return resolveinfo; 654 } else if (mWalletRoleObserver.isWalletRoleFeatureEnabled()) { 655 if (!aidDefaultInfo.walletDefaults.isEmpty()) { 656 // Check if any of the conflicting services is foreground default 657 if (conflictingDefaultInfo.foregroundDefault != null) { 658 // Conflicting AID registration is in foreground, trumps prefix tap&pay default 659 if (DBG) { 660 Log.d(TAG, "resolveAidConflictLocked: One of the conflicting AID " 661 + "registrations is foreground preferred, ignoring prefix"); 662 } 663 return EMPTY_RESOLVE_INFO; 664 } else { 665 // Prefix service is default wallet, treat as normal AID conflict for just prefix 666 if (DBG) { 667 Log.d(TAG, "resolveAidConflictLocked: Default wallet app exists. " 668 + "ignoring conflicting AIDs"); 669 } 670 resolveinfo = resolveAidConflictLocked(aidServices, true); 671 //If the AID is subsetAID check for prefix in all services. 672 if (isSubset(aidServices.get(0).aid)) { 673 resolveinfo.prefixInfo = findPrefixConflictForSubsetAid( 674 aidServices.get(0).aid, 675 List.of(resolveinfo.defaultService), true); 676 } 677 return resolveinfo; 678 } 679 } else { 680 if (conflictingDefaultInfo.foregroundDefault != null || 681 !conflictingDefaultInfo.walletDefaults.isEmpty()) { 682 if (DBG) { 683 Log.d(TAG, "resolveAidConflictLocked: One of the conflicting " 684 + "AID registrations " 685 + "is wallet holder or foreground preferred, ignoring prefix"); 686 } 687 return EMPTY_RESOLVE_INFO; 688 } else { 689 return noChildrenAidsPreferred(aidServices, conflictingServices); 690 } 691 } 692 } else if (aidDefaultInfo.paymentDefault != null) { 693 // Check if any of the conflicting services is foreground default 694 if (conflictingDefaultInfo.foregroundDefault != null) { 695 // Conflicting AID registration is in foreground, trumps prefix tap&pay default 696 if (DBG) { 697 Log.d(TAG, "resolveAidConflictLocked: One of the conflicting AID " 698 + "registrations is foreground preferred, ignoring prefix"); 699 } 700 return EMPTY_RESOLVE_INFO; 701 } else { 702 // Prefix service is tap&pay default, treat as normal AID conflict for just prefix 703 if (DBG) { 704 Log.d(TAG, 705 "resolveAidConflictLocked: Prefix AID service " 706 + aidDefaultInfo.paymentDefault.service.getComponent() 707 + " is payment" + " default, ignoring conflicting AIDs"); 708 } 709 resolveinfo = resolveAidConflictLocked(aidServices, true); 710 //If the AID is subsetAID check for prefix in same service. 711 if (isSubset(aidServices.get(0).aid)) { 712 resolveinfo.prefixInfo = findPrefixConflictForSubsetAid(aidServices.get(0).aid, 713 List.of(resolveinfo.defaultService), true); 714 } 715 return resolveinfo; 716 } 717 } else { 718 if (conflictingDefaultInfo.foregroundDefault != null || 719 conflictingDefaultInfo.paymentDefault != null) { 720 if (DBG) { 721 Log.d(TAG, 722 "resolveAidConflictLocked: One of the conflicting AID " 723 + "registrations is either payment " 724 + "default or foreground preferred, ignoring prefix."); 725 } 726 return EMPTY_RESOLVE_INFO; 727 } else { 728 return noChildrenAidsPreferred(aidServices, conflictingServices); 729 } 730 } 731 } 732 generateUserApduServiceInfoLocked(int userId, List<ApduServiceInfo> services)733 void generateUserApduServiceInfoLocked(int userId, List<ApduServiceInfo> services) { 734 mUserApduServiceInfo.put(userId, services); 735 } 736 getProfileParentId(int userId)737 private int getProfileParentId(int userId) { 738 UserHandle uh = null; 739 try { 740 UserManager um = mContext.createContextAsUser( 741 UserHandle.of(userId), /*flags=*/0) 742 .getSystemService(UserManager.class); 743 uh = um.getProfileParent(UserHandle.of(userId)); 744 } catch (IllegalStateException e) { 745 Log.d(TAG, "getProfileParentId: Failed to query parent id for " + userId); 746 } 747 return uh == null ? userId : uh.getIdentifier(); 748 } 749 generateServiceMapLocked(List<ApduServiceInfo> services)750 void generateServiceMapLocked(List<ApduServiceInfo> services) { 751 // Easiest is to just build the entire tree again 752 mAidServices.clear(); 753 int currentUser = ActivityManager.getCurrentUser(); 754 UserManager um = mContext.createContextAsUser( 755 UserHandle.of(currentUser), /*flags=*/0) 756 .getSystemService(UserManager.class); 757 758 for (Map.Entry<Integer, List<ApduServiceInfo>> entry : 759 mUserApduServiceInfo.entrySet()) { 760 if (currentUser != getProfileParentId(entry.getKey())) { 761 continue; 762 } 763 for (ApduServiceInfo service : entry.getValue()) { 764 if (VDBG) { 765 Log.d(TAG, "generateServiceMapLocked: component: " + service.getComponent()); 766 } 767 List<String> prefixAids = service.getPrefixAids(); 768 List<String> subSetAids = service.getSubsetAids(); 769 770 for (String aid : service.getAids()) { 771 if (!CardEmulation.isValidAid(aid)) { 772 Log.e(TAG, "generateServiceMapLocked: Aid " + aid + " is not valid."); 773 continue; 774 } 775 if (aid.endsWith("*") && !supportsAidPrefixRegistration()) { 776 Log.e(TAG, "generateServiceMapLocked: Prefix AID " + aid 777 + " ignored on device that doesn't support it."); 778 continue; 779 } else if (supportsAidPrefixRegistration() && prefixAids.size() > 0 780 && isExact(aid)) { 781 // Check if we already have an overlapping prefix registered for this AID 782 boolean foundPrefix = false; 783 for (String prefixAid : prefixAids) { 784 String prefix = prefixAid.substring(0, prefixAid.length() - 1); 785 if (aid.startsWith(prefix)) { 786 Log.e(TAG, 787 "generateServiceMapLocked: Ignoring exact AID " + aid 788 + " because prefix AID " + prefixAid 789 + " is already registered"); 790 foundPrefix = true; 791 break; 792 } 793 } 794 if (foundPrefix) { 795 continue; 796 } 797 } else if (aid.endsWith("#") && !supportsAidSubsetRegistration()) { 798 Log.e(TAG, "generateServiceMapLocked: Subset AID " + aid 799 + " ignored on device that doesn't support it."); 800 continue; 801 } else if (supportsAidSubsetRegistration() && subSetAids.size() > 0 802 && isExact(aid)) { 803 // Check if we already have an overlapping subset registered for this AID 804 boolean foundSubset = false; 805 for (String subsetAid : subSetAids) { 806 String plainSubset = subsetAid.substring(0, subsetAid.length() - 1); 807 if (plainSubset.startsWith(aid)) { 808 Log.e(TAG, 809 "generateServiceMapLocked: Ignoring exact AID " + aid 810 + " because subset AID " + plainSubset 811 + " is already registered"); 812 foundSubset = true; 813 break; 814 } 815 } 816 if (foundSubset) { 817 continue; 818 } 819 } 820 821 ServiceAidInfo serviceAidInfo = new ServiceAidInfo(); 822 serviceAidInfo.aid = aid.toUpperCase(); 823 serviceAidInfo.service = service; 824 serviceAidInfo.category = service.getCategoryForAid(aid); 825 826 if (mAidServices.containsKey(serviceAidInfo.aid)) { 827 final ArrayList<ServiceAidInfo> serviceAidInfos = 828 mAidServices.get(serviceAidInfo.aid); 829 serviceAidInfos.add(serviceAidInfo); 830 } else { 831 final ArrayList<ServiceAidInfo> serviceAidInfos = 832 new ArrayList<ServiceAidInfo>(); 833 serviceAidInfos.add(serviceAidInfo); 834 mAidServices.put(serviceAidInfo.aid, serviceAidInfos); 835 } 836 } 837 } 838 } 839 } 840 isExact(String aid)841 static boolean isExact(String aid) { 842 return aid == null ? false : !(aid.endsWith("*") || aid.endsWith("#")); 843 } 844 isPrefix(String aid)845 static boolean isPrefix(String aid) { 846 return aid == null ? false : aid.endsWith("*"); 847 } 848 isSubset(String aid)849 static boolean isSubset(String aid) { 850 return aid == null ? false : aid.endsWith("#"); 851 } 852 853 final class ResolvedPrefixConflictAid { 854 String prefixAid = null; 855 boolean matchingSubset = false; 856 } 857 858 final class AidConflicts { 859 NavigableMap<String, ArrayList<ServiceAidInfo>> conflictMap; 860 final ArrayList<ServiceAidInfo> services = new ArrayList<ServiceAidInfo>(); 861 final HashSet<String> aids = new HashSet<String>(); 862 } 863 findPrefixConflictForSubsetAid(String subsetAid , List<ApduServiceInfo> prefixServices, boolean priorityRootAid)864 ResolvedPrefixConflictAid findPrefixConflictForSubsetAid(String subsetAid , 865 List<ApduServiceInfo> prefixServices, boolean priorityRootAid) { 866 ArrayList<String> prefixAids = new ArrayList<String>(); 867 String minPrefix = null; 868 //This functions checks whether there is a prefix AID matching to subset AID 869 //Because both the subset AID and matching smaller perfix are to be added to routing table. 870 //1.Finds the prefix matching AID in the services sent. 871 //2.Find the smallest prefix among matching prefix and add it only if it is not same as susbet AID. 872 //3..If the subset AID and prefix AID are same add only one AID with both prefix , subset bits set. 873 // Cut off "#" 874 String plainSubsetAid = subsetAid.substring(0, subsetAid.length() - 1); 875 for (ApduServiceInfo service : prefixServices) { 876 for (String prefixAid : service.getPrefixAids()) { 877 // Cut off "#" 878 String plainPrefix= prefixAid.substring(0, prefixAid.length() - 1); 879 if (plainSubsetAid.startsWith(plainPrefix)) { 880 if (priorityRootAid) { 881 int userId = UserHandle.getUserHandleForUid(service.getUid()) 882 .getIdentifier(); 883 if (CardEmulation.CATEGORY_PAYMENT 884 .equals(service.getCategoryForAid(prefixAid)) || 885 (service.getComponent().equals(mPreferredForegroundService) && 886 userId == mUserIdPreferredForegroundService)) 887 prefixAids.add(prefixAid); 888 } else { 889 prefixAids.add(prefixAid); 890 } 891 } 892 } 893 } 894 if (prefixAids.size() > 0) 895 minPrefix = Collections.min(prefixAids); 896 ResolvedPrefixConflictAid resolvedPrefix = new ResolvedPrefixConflictAid(); 897 resolvedPrefix.prefixAid = minPrefix; 898 if ((minPrefix != null ) && 899 plainSubsetAid.equalsIgnoreCase(minPrefix.substring(0, minPrefix.length() - 1))) 900 resolvedPrefix.matchingSubset = true; 901 return resolvedPrefix; 902 } 903 findConflictsForPrefixLocked(String prefixAid)904 AidConflicts findConflictsForPrefixLocked(String prefixAid) { 905 AidConflicts prefixConflicts = new AidConflicts(); 906 String plainAid = prefixAid.substring(0, prefixAid.length() - 1); // Cut off "*" 907 String lastAidWithPrefix = String.format("%-32s", plainAid).replace(' ', 'F'); 908 if (DBG) { 909 Log.d(TAG, "findConflictsForPrefixLocked: Finding AIDs in range [" + plainAid + " - " 910 + lastAidWithPrefix + "]"); 911 } 912 prefixConflicts.conflictMap = 913 mAidServices.subMap(plainAid, true, lastAidWithPrefix, true); 914 for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry : 915 prefixConflicts.conflictMap.entrySet()) { 916 if (!entry.getKey().equalsIgnoreCase(prefixAid)) { 917 if (DBG) 918 Log.d(TAG, 919 "findConflictsForPrefixLocked: AID " + entry.getKey() 920 + " conflicts with prefix; " 921 + " adding handling services for conflict resolution."); 922 prefixConflicts.services.addAll(entry.getValue()); 923 prefixConflicts.aids.add(entry.getKey()); 924 } 925 } 926 return prefixConflicts; 927 } 928 findConflictsForSubsetAidLocked(String subsetAid)929 AidConflicts findConflictsForSubsetAidLocked(String subsetAid) { 930 AidConflicts subsetConflicts = new AidConflicts(); 931 // Cut off "@" 932 String lastPlainAid = subsetAid.substring(0, subsetAid.length() - 1); 933 // Cut off "@" 934 String plainSubsetAid = subsetAid.substring(0, subsetAid.length() - 1); 935 String firstAid = subsetAid.substring(0, 10); 936 if (DBG) { 937 Log.d(TAG, "findConflictsForSubsetAidLocked: Finding AIDs in range [" + firstAid + " - " 938 + lastPlainAid + "]"); 939 } 940 subsetConflicts.conflictMap = new TreeMap(); 941 for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry : 942 mAidServices.entrySet()) { 943 String aid = entry.getKey(); 944 String plainAid = aid; 945 if (isSubset(aid) || isPrefix(aid)) 946 plainAid = aid.substring(0, aid.length() - 1); 947 if (plainSubsetAid.startsWith(plainAid)) 948 subsetConflicts.conflictMap.put(entry.getKey(),entry.getValue()); 949 } 950 for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry : 951 subsetConflicts.conflictMap.entrySet()) { 952 if (!entry.getKey().equalsIgnoreCase(subsetAid)) { 953 if (DBG) 954 Log.d(TAG, 955 "findConflictsForSubsetAidLocked: AID " + entry.getKey() 956 + " conflicts with subset AID; " 957 + " adding handling services for conflict resolution."); 958 subsetConflicts.services.addAll(entry.getValue()); 959 subsetConflicts.aids.add(entry.getKey()); 960 } 961 } 962 return subsetConflicts; 963 } 964 generateAidCacheLocked()965 void generateAidCacheLocked() { 966 mAidCache.clear(); 967 // Get all exact and prefix AIDs in an ordered list 968 final TreeMap<String, AidResolveInfo> aidCache = new TreeMap<String, AidResolveInfo>(); 969 970 //aidCache is temproary cache for geenrating the first prefix based lookup table. 971 PriorityQueue<String> aidsToResolve = new PriorityQueue<String>(mAidServices.keySet()); 972 aidCache.clear(); 973 while (!aidsToResolve.isEmpty()) { 974 final ArrayList<String> resolvedAids = new ArrayList<String>(); 975 976 String aidToResolve = aidsToResolve.peek(); 977 // Because of the lexicographical ordering, all following AIDs either start with the 978 // same bytes and are longer, or start with different bytes. 979 980 // A special case is if another service registered the same AID as a prefix, in 981 // which case we want to start with that AID, since it conflicts with this one 982 // All exact and suffix and prefix AID must be checked for conflicting cases 983 if (aidsToResolve.contains(aidToResolve + "*")) { 984 aidToResolve = aidToResolve + "*"; 985 } 986 if (DBG) Log.d(TAG, "generateAidCacheLocked: starting with aid " + aidToResolve); 987 988 if (isPrefix(aidToResolve)) { 989 // This AID itself is a prefix; let's consider this prefix as the "root", 990 // and all conflicting AIDs as its children. 991 // For example, if "A000000003*" is the prefix root, 992 // "A000000003", "A00000000301*", "A0000000030102" are all conflicting children AIDs 993 final ArrayList<ServiceAidInfo> prefixServices = new ArrayList<ServiceAidInfo>( 994 mAidServices.get(aidToResolve)); 995 996 // Find all conflicting children services 997 AidConflicts prefixConflicts = findConflictsForPrefixLocked(aidToResolve); 998 999 // Resolve conflicts 1000 AidResolveInfo resolveInfo = resolveAidConflictLocked(prefixServices, 1001 prefixConflicts.services); 1002 aidCache.put(aidToResolve, resolveInfo); 1003 resolvedAids.add(aidToResolve); 1004 if (resolveInfo.defaultService != null) { 1005 // This prefix is the default; therefore, AIDs of all conflicting children 1006 // will no longer be evaluated. 1007 resolvedAids.addAll(prefixConflicts.aids); 1008 for (String aid : resolveInfo.defaultService.getSubsetAids()) { 1009 if (prefixConflicts.aids.contains(aid)) { 1010 int userId = UserHandle. 1011 getUserHandleForUid(resolveInfo.defaultService.getUid()). 1012 getIdentifier(); 1013 if ((CardEmulation.CATEGORY_PAYMENT. 1014 equals(resolveInfo.defaultService.getCategoryForAid(aid))) || 1015 (resolveInfo.defaultService.getComponent(). 1016 equals(mPreferredForegroundService) && 1017 userId == mUserIdPreferredForegroundService)) { 1018 AidResolveInfo childResolveInfo = resolveAidConflictLocked(mAidServices.get(aid), false); 1019 aidCache.put(aid,childResolveInfo); 1020 Log.d(TAG, "generateAidCacheLocked: AID " + aid 1021 + " shared with prefix; adding subset ."); 1022 } 1023 } 1024 } 1025 } else if (resolveInfo.services.size() > 0) { 1026 // This means we don't have a default for this prefix and all its 1027 // conflicting children. So, for all conflicting AIDs, just add 1028 // all handling services without setting a default 1029 boolean foundChildService = false; 1030 for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry : 1031 prefixConflicts.conflictMap.entrySet()) { 1032 if (!entry.getKey().equalsIgnoreCase(aidToResolve)) { 1033 if (DBG) 1034 Log.d(TAG, 1035 "generateAidCacheLocked: AID " + entry.getKey() 1036 + " shared with prefix; " 1037 + " adding all handling services."); 1038 AidResolveInfo childResolveInfo = resolveAidConflictLocked( 1039 entry.getValue(), false); 1040 // Special case: in this case all children AIDs must be routed to the 1041 // host, so we can ask the user which service is preferred. 1042 // Since these are all "children" of the prefix, they don't need 1043 // to be routed, since the prefix will already get routed to the host 1044 ArrayList<ApduServiceInfo> list = new ArrayList<>(); 1045 for (int i = 0; i < entry.getValue().size(); i++) { 1046 list.add(entry.getValue().get(i).service); 1047 } 1048 resolveInfo.services.addAll(list); 1049 aidCache.put(aidToResolve, resolveInfo); 1050 resolvedAids.add(entry.getKey()); 1051 foundChildService |= !childResolveInfo.services.isEmpty(); 1052 } 1053 } 1054 // Special case: if in the end we didn't add any children services, 1055 // and the prefix has only one service, make that default 1056 if (!foundChildService && resolveInfo.services.size() == 1) { 1057 resolveInfo.defaultService = resolveInfo.services.get(0); 1058 } 1059 } else { 1060 // This prefix is not handled at all; we will evaluate 1061 // the children separately in next passes. 1062 } 1063 } else { 1064 // Exact AID and no other conflicting AID registrations present 1065 // This is true because aidsToResolve is lexicographically ordered, and 1066 // so by necessity all other AIDs are different than this AID or longer. 1067 if (DBG) Log.d(TAG, "generateAidCacheLocked: Exact AID, resolving."); 1068 final ArrayList<ServiceAidInfo> conflictingServiceInfos = 1069 new ArrayList<ServiceAidInfo>(mAidServices.get(aidToResolve)); 1070 aidCache.put(aidToResolve, resolveAidConflictLocked(conflictingServiceInfos, true)); 1071 resolvedAids.add(aidToResolve); 1072 } 1073 1074 // Remove the AIDs we resolved from the list of AIDs to resolve 1075 if (DBG) { 1076 Log.d(TAG, "generateAidCacheLocked: AIDs: " + resolvedAids + " were resolved."); 1077 } 1078 aidsToResolve.removeAll(resolvedAids); 1079 resolvedAids.clear(); 1080 } 1081 PriorityQueue<String> reversedQueue = new PriorityQueue<String>(1, Collections.reverseOrder()); 1082 reversedQueue.addAll(aidCache.keySet()); 1083 while (!reversedQueue.isEmpty()) { 1084 final ArrayList<String> resolvedAids = new ArrayList<String>(); 1085 1086 String aidToResolve = reversedQueue.peek(); 1087 if (isPrefix(aidToResolve)) { 1088 String matchingSubset = aidToResolve.substring(0, aidToResolve.length() - 1) + "#"; 1089 if (DBG) { 1090 Log.d(TAG, "generateAidCacheLocked: matching subset" + matchingSubset); 1091 } 1092 if (reversedQueue.contains(matchingSubset)) 1093 aidToResolve = aidToResolve.substring(0,aidToResolve.length()-1) + "#"; 1094 } 1095 if (isSubset(aidToResolve)) { 1096 if (DBG) { 1097 Log.d(TAG, "generateAidCacheLocked: subset resolving aidToResolve " 1098 + aidToResolve); 1099 } 1100 final ArrayList<ServiceAidInfo> subsetServices = new ArrayList<ServiceAidInfo>( 1101 mAidServices.get(aidToResolve)); 1102 1103 // Find all conflicting children services 1104 AidConflicts aidConflicts = findConflictsForSubsetAidLocked(aidToResolve); 1105 1106 // Resolve conflicts 1107 AidResolveInfo resolveInfo = resolveAidConflictLocked(subsetServices, 1108 aidConflicts.services); 1109 mAidCache.put(aidToResolve, resolveInfo); 1110 resolvedAids.add(aidToResolve); 1111 if (resolveInfo.defaultService != null) { 1112 // This subset is the default; therefore, AIDs of all conflicting children 1113 // will no longer be evaluated.Check for any prefix matching in the same service 1114 if (resolveInfo.prefixInfo != null && resolveInfo.prefixInfo.prefixAid != null && 1115 !resolveInfo.prefixInfo.matchingSubset) { 1116 if (DBG) 1117 Log.d(TAG, 1118 "generateAidCacheLocked: AID default " 1119 + resolveInfo.prefixInfo.prefixAid 1120 + " prefix AID shared with dsubset root; " 1121 + " adding prefix aid"); 1122 AidResolveInfo childResolveInfo = resolveAidConflictLocked( 1123 mAidServices.get(resolveInfo.prefixInfo.prefixAid), false); 1124 mAidCache.put(resolveInfo.prefixInfo.prefixAid, childResolveInfo); 1125 } 1126 resolvedAids.addAll(aidConflicts.aids); 1127 } else if (resolveInfo.services.size() > 0) { 1128 // This means we don't have a default for this subset and all its 1129 // conflicting children. So, for all conflicting AIDs, just add 1130 // all handling services without setting a default 1131 boolean foundChildService = false; 1132 for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry : 1133 aidConflicts.conflictMap.entrySet()) { 1134 // We need to add shortest prefix among them. 1135 if (!entry.getKey().equalsIgnoreCase(aidToResolve)) { 1136 if (DBG) 1137 Log.d(TAG, 1138 "generateAidCacheLocked: AID " + entry.getKey() 1139 + " shared with subset root; " 1140 + " adding all handling services."); 1141 AidResolveInfo childResolveInfo = resolveAidConflictLocked( 1142 entry.getValue(), false); 1143 // Special case: in this case all children AIDs must be routed to the 1144 // host, so we can ask the user which service is preferred. 1145 // Since these are all "children" of the subset, they don't need 1146 // to be routed, since the subset will already get routed to the host 1147 ArrayList<ApduServiceInfo> list = new ArrayList<>(); 1148 for (int i = 0; i < entry.getValue().size(); i++) { 1149 list.add(entry.getValue().get(i).service); 1150 } 1151 resolveInfo.services.addAll(list); 1152 aidCache.put(aidToResolve, resolveInfo); 1153 resolvedAids.add(entry.getKey()); 1154 foundChildService |= !childResolveInfo.services.isEmpty(); 1155 } 1156 } 1157 if (resolveInfo.prefixInfo != null 1158 && resolveInfo.prefixInfo.prefixAid != null 1159 && !resolveInfo.prefixInfo.matchingSubset) { 1160 AidResolveInfo childResolveInfo = resolveAidConflictLocked( 1161 mAidServices.get(resolveInfo.prefixInfo.prefixAid), false); 1162 mAidCache.put(resolveInfo.prefixInfo.prefixAid, childResolveInfo); 1163 if (DBG) 1164 Log.d(TAG, 1165 "generateAidCacheLocked: AID " 1166 + resolveInfo.prefixInfo.prefixAid 1167 + " prefix AID shared with subset root; " 1168 + " adding prefix aid"); 1169 } 1170 // Special case: if in the end we didn't add any children services, 1171 // and the subset has only one service, make that default 1172 if (!foundChildService && resolveInfo.services.size() == 1) { 1173 resolveInfo.defaultService = resolveInfo.services.get(0); 1174 } 1175 } else { 1176 // This subset is not handled at all; we will evaluate 1177 // the children separately in next passes. 1178 } 1179 } else { 1180 // Exact AID and no other conflicting AID registrations present. This is 1181 // true because reversedQueue is lexicographically ordered in revrese, and 1182 // so by necessity all other AIDs are different than this AID or shorter. 1183 if (DBG) { 1184 Log.d(TAG, "generateAidCacheLocked: Exact or Prefix AID." + aidToResolve); 1185 } 1186 mAidCache.put(aidToResolve, aidCache.get(aidToResolve)); 1187 resolvedAids.add(aidToResolve); 1188 } 1189 1190 // Remove the AIDs we resolved from the list of AIDs to resolve 1191 if (DBG) { 1192 Log.d(TAG, "generateAidCacheLocked: AIDs: " + resolvedAids + " were resolved."); 1193 } 1194 reversedQueue.removeAll(resolvedAids); 1195 resolvedAids.clear(); 1196 } 1197 if (DBG) { 1198 for (String key : mAidCache.keySet()) { 1199 Log.d(TAG, "generateAidCacheLocked: aid cache entry" + key + " val:" 1200 + mAidCache.get(key).toString()); 1201 } 1202 } 1203 updateRoutingLocked(false, false); 1204 } 1205 computeAidPowerState(boolean isOnHost, boolean requiresScreenOn, boolean requiresUnlock)1206 private int computeAidPowerState(boolean isOnHost, boolean requiresScreenOn, 1207 boolean requiresUnlock) { 1208 int power = POWER_STATE_ALL; 1209 if (NfcService.getInstance().getNciVersion() < NfcService.getInstance().NCI_VERSION_2_0) { 1210 power = POWER_STATE_ALL_NCI_VERSION_1_0; 1211 } 1212 1213 if (isOnHost) { 1214 power &= ~(POWER_STATE_SWITCH_OFF | POWER_STATE_BATTERY_OFF); 1215 } else { 1216 if (requiresUnlock) { 1217 power &= ~POWER_STATE_SCREEN_ON_LOCKED; 1218 } 1219 } 1220 1221 if (requiresScreenOn) { 1222 power &= ~(POWER_STATE_SWITCH_OFF | POWER_STATE_BATTERY_OFF 1223 | POWER_STATE_SCREEN_OFF_UNLOCKED | POWER_STATE_SCREEN_OFF_LOCKED); 1224 } 1225 if (requiresUnlock) { 1226 power &= ~(POWER_STATE_SWITCH_OFF | POWER_STATE_BATTERY_OFF 1227 | POWER_STATE_SCREEN_OFF_LOCKED); 1228 } 1229 1230 return power; 1231 } 1232 setOemExtension(INfcOemExtensionCallback nfcOemExtensionCallback)1233 public void setOemExtension(INfcOemExtensionCallback nfcOemExtensionCallback) { 1234 mNfcOemExtensionCallback = nfcOemExtensionCallback; 1235 } 1236 1237 @AidRoutingManager.ConfigureRoutingResult updateRoutingLocked(boolean force, boolean isOverrideOrRecover)1238 public int updateRoutingLocked(boolean force, boolean isOverrideOrRecover) { 1239 if (!mNfcEnabled) { 1240 if (DBG) { 1241 Log.d(TAG, 1242 "updateRoutingLocked: Not updating routing table " + "because NFC is off"); 1243 } 1244 return AidRoutingManager.CONFIGURE_ROUTING_FAILURE_UNKNOWN; 1245 } 1246 final HashMap<String, AidRoutingManager.AidEntry> routingEntries = new HashMap<>(); 1247 boolean requiresScreenOnServiceExist = false; 1248 // For each AID, find interested services 1249 for (Map.Entry<String, AidResolveInfo> aidEntry: 1250 mAidCache.entrySet()) { 1251 String aid = aidEntry.getKey(); 1252 AidResolveInfo resolveInfo = aidEntry.getValue(); 1253 AidRoutingManager.AidEntry aidType = mRoutingManager.new AidEntry(); 1254 if (aid.endsWith("#")) { 1255 aidType.aidInfo |= AID_ROUTE_QUAL_SUBSET; 1256 } 1257 if (aid.endsWith("*") || (resolveInfo.prefixInfo != null 1258 && resolveInfo.prefixInfo.matchingSubset)) { 1259 aidType.aidInfo |= AID_ROUTE_QUAL_PREFIX; 1260 } 1261 if (resolveInfo.services.isEmpty()) { 1262 // No interested services 1263 // prevent unchecked offhost aids route to offhostSE 1264 if (!resolveInfo.unCheckedOffHostSecureElement.isEmpty()) { 1265 aidType.unCheckedOffHostSE.addAll(resolveInfo.unCheckedOffHostSecureElement); 1266 aidType.isOnHost = true; 1267 aidType.power = POWER_STATE_SWITCH_ON; 1268 routingEntries.put(aid, aidType); 1269 force = true; 1270 } 1271 } else if (resolveInfo.defaultService != null) { 1272 // There is a default service set, route to where that service resides - 1273 // either on the host (HCE) or on an SE. 1274 aidType.isOnHost = resolveInfo.defaultService.isOnHost(); 1275 if (!aidType.isOnHost) { 1276 aidType.offHostSE = 1277 resolveInfo.defaultService.getOffHostSecureElement(); 1278 } 1279 1280 boolean requiresUnlock = resolveInfo.defaultService.requiresUnlock(); 1281 boolean requiresScreenOn = resolveInfo.defaultService.requiresScreenOn(); 1282 requiresScreenOnServiceExist |= requiresScreenOn; 1283 aidType.power = 1284 computeAidPowerState(aidType.isOnHost, requiresScreenOn, requiresUnlock); 1285 1286 routingEntries.put(aid, aidType); 1287 } else if (resolveInfo.services.size() == 1) { 1288 // Only one service, but not the default, must route to host 1289 // to ask the user to choose one. 1290 if (resolveInfo.category.equals( 1291 CardEmulation.CATEGORY_PAYMENT)) { 1292 aidType.isOnHost = true; 1293 } else { 1294 aidType.isOnHost = resolveInfo.services.get(0).isOnHost(); 1295 if (!aidType.isOnHost) { 1296 aidType.offHostSE = 1297 resolveInfo.services.get(0).getOffHostSecureElement(); 1298 } 1299 } 1300 1301 boolean requiresUnlock = resolveInfo.services.get(0).requiresUnlock(); 1302 boolean requiresScreenOn = resolveInfo.services.get(0).requiresScreenOn(); 1303 requiresScreenOnServiceExist |= requiresScreenOn; 1304 aidType.power = 1305 computeAidPowerState(aidType.isOnHost, requiresScreenOn, requiresUnlock); 1306 1307 routingEntries.put(aid, aidType); 1308 } else if (resolveInfo.services.size() > 1) { 1309 // Multiple services if all the services are routing to same 1310 // offhost then the service should be routed to off host. 1311 boolean onHost = false; 1312 String offHostSE = null; 1313 boolean requiresUnlock = false; 1314 boolean requiresScreenOn = true; 1315 for (ApduServiceInfo service : resolveInfo.services) { 1316 // In case there is at least one service which routes to host 1317 // Route it to host for user to select which service to use 1318 onHost |= service.isOnHost(); 1319 if (!onHost) { 1320 if (offHostSE == null) { 1321 offHostSE = service.getOffHostSecureElement(); 1322 requiresUnlock = service.requiresUnlock(); 1323 requiresScreenOn = service.requiresScreenOn(); 1324 } else if (!offHostSE.equals( 1325 service.getOffHostSecureElement())) { 1326 // There are registrations to different SEs, route this 1327 // to host and have user choose a service for this AID 1328 offHostSE = null; 1329 onHost = true; 1330 requiresUnlock = false; 1331 requiresScreenOn = true; 1332 break; 1333 } else if (requiresUnlock != service.requiresUnlock() 1334 || requiresScreenOn != service.requiresScreenOn()) { 1335 // There are registrations to the same SE with differernt supported 1336 // power states, route this to host and have user choose a service 1337 // for this AID 1338 offHostSE = null; 1339 onHost = true; 1340 requiresUnlock = false; 1341 requiresScreenOn = true; 1342 break; 1343 } 1344 } 1345 requiresScreenOnServiceExist |= service.requiresScreenOn(); 1346 } 1347 aidType.isOnHost = onHost; 1348 aidType.offHostSE = onHost ? null : offHostSE; 1349 requiresUnlock = onHost ? false : requiresUnlock; 1350 requiresScreenOn = onHost ? true : requiresScreenOn; 1351 1352 aidType.power = computeAidPowerState(onHost, requiresScreenOn, requiresUnlock); 1353 1354 routingEntries.put(aid, aidType); 1355 } 1356 } 1357 mRequiresScreenOnServiceExist = requiresScreenOnServiceExist; 1358 int result = mRoutingManager.configureRouting(routingEntries, force, isOverrideOrRecover); 1359 if (result == AidRoutingManager.CONFIGURE_ROUTING_FAILURE_TABLE_FULL 1360 && mNfcOemExtensionCallback != null) { 1361 try { 1362 mNfcOemExtensionCallback.onRoutingTableFull(); 1363 } catch (RemoteException exception) { 1364 Log.e(TAG, "updateRoutingLocked: Error in onLaunchRoutingTableFullDialog: " 1365 + exception); 1366 } 1367 } 1368 return result; 1369 } 1370 onServicesUpdated(int userId, List<ApduServiceInfo> services)1371 public void onServicesUpdated(int userId, List<ApduServiceInfo> services) { 1372 if (DBG) Log.d(TAG, "onServicesUpdated"); 1373 synchronized (mLock) { 1374 generateUserApduServiceInfoLocked(userId, services); 1375 // Rebuild our internal data-structures 1376 generateAssociatedRoleServicesLocked(userId); 1377 generateServiceMapLocked(services); 1378 generateAidCacheLocked(); 1379 } 1380 } 1381 onPreferredPaymentServiceChanged(ComponentNameAndUser service)1382 public void onPreferredPaymentServiceChanged(ComponentNameAndUser service) { 1383 if (DBG) Log.d(TAG, "onPreferredPaymentServiceChanged: user:" + service.getUserId()); 1384 synchronized (mLock) { 1385 mPreferredPaymentService = service.getComponentName(); 1386 mUserIdPreferredPaymentService = service.getUserId(); 1387 generateAidCacheLocked(); 1388 } 1389 } 1390 onPreferredForegroundServiceChanged(ComponentNameAndUser service)1391 public void onPreferredForegroundServiceChanged(ComponentNameAndUser service) { 1392 if (DBG) Log.d(TAG, "onPreferredForegroundServiceChanged: user:" + service.getUserId()); 1393 synchronized (mLock) { 1394 mPreferredForegroundService = service.getComponentName(); 1395 mUserIdPreferredForegroundService = service.getUserId(); 1396 generateAidCacheLocked(); 1397 } 1398 } 1399 onWalletRoleHolderChanged(String defaultWalletHolderPackageName, int userId)1400 public void onWalletRoleHolderChanged(String defaultWalletHolderPackageName, int userId) { 1401 if (DBG) Log.d(TAG, "onWalletRoleHolderChanged: user:" + userId); 1402 synchronized (mLock) { 1403 mDefaultWalletHolderPackageName = defaultWalletHolderPackageName; 1404 mUserIdDefaultWalletHolder = userId; 1405 generateAssociatedRoleServicesLocked(userId); 1406 generateAidCacheLocked(); 1407 } 1408 } 1409 1410 @SuppressLint("NewApi") generateAssociatedRoleServicesLocked(int userId)1411 private void generateAssociatedRoleServicesLocked(int userId) { 1412 if (!Flags.nfcAssociatedRoleServices()) { 1413 return; 1414 } 1415 1416 mAssociatedRoleServices.clear(); 1417 1418 if (mDefaultWalletHolderPackageName == null || userId != mUserIdDefaultWalletHolder) { 1419 return; 1420 } 1421 1422 List<ApduServiceInfo> apduServices = mUserApduServiceInfo.get(userId); 1423 if (apduServices == null) { 1424 return; 1425 } 1426 1427 PackageManager pm = mContext.getPackageManager(); 1428 1429 try { 1430 PackageManager.Property prop = pm.getProperty( 1431 CardEmulation.PROPERTY_ALLOW_SHARED_ROLE_PRIORITY, 1432 mDefaultWalletHolderPackageName); 1433 if (!prop.getBoolean()) { 1434 return; 1435 } 1436 } catch (PackageManager.NameNotFoundException e) { 1437 // Role owner does not want to share priority with anyone else 1438 return; 1439 } 1440 1441 for (ApduServiceInfo service : apduServices) { 1442 if (service.getComponent().getPackageName().equals(mDefaultWalletHolderPackageName)) { 1443 continue; 1444 } 1445 1446 if (service.wantsRoleHolderPriority() && pm.checkSignatures(mDefaultWalletHolderPackageName, 1447 service.getComponent().getPackageName()) == PackageManager.SIGNATURE_MATCH) { 1448 mAssociatedRoleServices.add(service); 1449 } 1450 } 1451 } 1452 1453 @NonNull getPreferredService()1454 public ComponentNameAndUser getPreferredService() { 1455 if (mPreferredForegroundService != null) { 1456 // return current foreground service 1457 return new ComponentNameAndUser( 1458 mUserIdPreferredForegroundService, mPreferredForegroundService); 1459 } else { 1460 // return current preferred service 1461 return getPreferredPaymentService(); 1462 } 1463 } 1464 1465 @NonNull getPreferredPaymentService()1466 public ComponentNameAndUser getPreferredPaymentService() { 1467 return new ComponentNameAndUser(mUserIdPreferredPaymentService, mPreferredPaymentService); 1468 } 1469 isPreferredServicePackageNameForUser(String packageName, int userId)1470 public boolean isPreferredServicePackageNameForUser(String packageName, int userId) { 1471 if (mPreferredForegroundService != null) { 1472 if (mPreferredForegroundService.getPackageName().equals(packageName) && 1473 userId == mUserIdPreferredForegroundService) { 1474 return true; 1475 } else { 1476 Log.i(TAG, 1477 "isPreferredServicePackageNameForUser: NfcService:" + packageName + "(" 1478 + userId + ") is not equal to the foreground service " 1479 + mPreferredForegroundService + "(" 1480 + mUserIdPreferredForegroundService + ")"); 1481 return false; 1482 } 1483 } else if (mWalletRoleObserver.isWalletRoleFeatureEnabled()) { 1484 if (isDefaultOrAssociatedWalletPackage(packageName, userId)) { 1485 return true; 1486 } else { 1487 Log.i(TAG, "isPreferredServicePackageNameForUser: NfcService:" + packageName + "(" 1488 + userId + ") is not equal to the default wallet service " 1489 + mDefaultWalletHolderPackageName + "(" + mUserIdDefaultWalletHolder + ")"); 1490 return false; 1491 } 1492 } else if (mPreferredPaymentService != null && 1493 userId == mUserIdPreferredPaymentService && 1494 mPreferredPaymentService.getPackageName().equals(packageName)) { 1495 return true; 1496 } else { 1497 Log.i(TAG, "isPreferredServicePackageNameForUser: NfcService:" + packageName + "(" 1498 + userId + ") is not equal to the default payment service " 1499 + mPreferredPaymentService + "(" + mUserIdPreferredPaymentService + ")"); 1500 return false; 1501 } 1502 } 1503 1504 onNfcDisabled()1505 public void onNfcDisabled() { 1506 synchronized (mLock) { 1507 mNfcEnabled = false; 1508 } 1509 mRoutingManager.onNfccRoutingTableCleared(); 1510 } 1511 onNfcEnabled()1512 public void onNfcEnabled() { 1513 synchronized (mLock) { 1514 mNfcEnabled = true; 1515 updateRoutingLocked(true, false); 1516 } 1517 } 1518 onTriggerRoutingTableUpdate()1519 public void onTriggerRoutingTableUpdate() { 1520 synchronized (mLock) { 1521 updateRoutingLocked(true, false); 1522 } 1523 } 1524 1525 @AidRoutingManager.ConfigureRoutingResult onRoutingOverridedOrRecovered()1526 public int onRoutingOverridedOrRecovered() { 1527 synchronized (mLock) { 1528 return updateRoutingLocked(true, true); 1529 } 1530 } 1531 dumpEntry(Map.Entry<String, AidResolveInfo> entry)1532 String dumpEntry(Map.Entry<String, AidResolveInfo> entry) { 1533 StringBuilder sb = new StringBuilder(); 1534 String category = entry.getValue().category; 1535 ApduServiceInfo defaultServiceInfo = entry.getValue().defaultService; 1536 sb.append(" \"" + entry.getKey() + "\" (category: " + category + ")\n"); 1537 ComponentName defaultComponent = defaultServiceInfo != null ? 1538 defaultServiceInfo.getComponent() : null; 1539 1540 for (ApduServiceInfo serviceInfo : entry.getValue().services) { 1541 sb.append(" "); 1542 if (serviceInfo.equals(defaultServiceInfo)) { 1543 sb.append("*DEFAULT* "); 1544 } 1545 sb.append(serviceInfo + 1546 " (Description: " + serviceInfo.getDescription() + ")\n"); 1547 } 1548 return sb.toString(); 1549 } 1550 dump(FileDescriptor fd, PrintWriter pw, String[] args)1551 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 1552 pw.println(" AID cache entries: "); 1553 for (Map.Entry<String, AidResolveInfo> entry : mAidCache.entrySet()) { 1554 pw.println(dumpEntry(entry)); 1555 } 1556 pw.println(" Service preferred by foreground app: " + mPreferredForegroundService); 1557 pw.println(" UserId: " + mUserIdPreferredForegroundService); 1558 pw.println(" Preferred payment service: " + mPreferredPaymentService); 1559 pw.println(" UserId: " + mUserIdPreferredPaymentService); 1560 if (Flags.nfcAssociatedRoleServices()) { 1561 pw.println(" Wallet role userId: " + mUserIdDefaultWalletHolder); 1562 pw.println(" Wallet role package: " + mDefaultWalletHolderPackageName); 1563 pw.println(" Associated role services: " + mAssociatedRoleServices.stream() 1564 .map(service -> service.getComponent().toString()).toList()); 1565 } 1566 pw.println(""); 1567 mRoutingManager.dump(fd, pw, args); 1568 pw.println(""); 1569 } 1570 1571 /** 1572 * Dump debugging information as a RegisteredAidCacheProto 1573 * 1574 * Note: 1575 * See proto definition in frameworks/base/core/proto/android/nfc/card_emulation.proto 1576 * When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and 1577 * {@link ProtoOutputStream#end(long)} after. 1578 * Never reuse a proto field number. When removing a field, mark it as reserved. 1579 */ dumpDebug(ProtoOutputStream proto)1580 void dumpDebug(ProtoOutputStream proto) { 1581 for (Map.Entry<String, AidResolveInfo> entry : mAidCache.entrySet()) { 1582 long token = proto.start(RegisteredAidCacheProto.AID_CACHE_ENTRIES); 1583 proto.write(RegisteredAidCacheProto.AidCacheEntry.KEY, entry.getKey()); 1584 proto.write(RegisteredAidCacheProto.AidCacheEntry.CATEGORY, entry.getValue().category); 1585 ApduServiceInfo defaultServiceInfo = entry.getValue().defaultService; 1586 ComponentName defaultComponent = defaultServiceInfo != null ? 1587 defaultServiceInfo.getComponent() : null; 1588 if (defaultComponent != null) { 1589 Utils.dumpDebugComponentName( 1590 defaultComponent, proto, 1591 RegisteredAidCacheProto.AidCacheEntry.DEFAULT_COMPONENT); 1592 } 1593 for (ApduServiceInfo serviceInfo : entry.getValue().services) { 1594 long sToken = proto.start(RegisteredAidCacheProto.AidCacheEntry.SERVICES); 1595 serviceInfo.dumpDebug(proto); 1596 proto.end(sToken); 1597 } 1598 proto.end(token); 1599 } 1600 if (mPreferredForegroundService != null) { 1601 Utils.dumpDebugComponentName( 1602 mPreferredForegroundService, proto, 1603 RegisteredAidCacheProto.PREFERRED_FOREGROUND_SERVICE); 1604 } 1605 if (mPreferredPaymentService != null) { 1606 Utils.dumpDebugComponentName( 1607 mPreferredPaymentService, proto, 1608 RegisteredAidCacheProto.PREFERRED_PAYMENT_SERVICE); 1609 } 1610 long token = proto.start(RegisteredAidCacheProto.ROUTING_MANAGER); 1611 mRoutingManager.dumpDebug(proto); 1612 proto.end(token); 1613 } 1614 onPreferredSimChanged(int simType)1615 public void onPreferredSimChanged(int simType) { 1616 synchronized (mLock) { 1617 mPreferredSimType = simType; 1618 mRoutingManager.onNfccRoutingTableCleared(); 1619 generateAidCacheLocked(); 1620 } 1621 } 1622 } 1623