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