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