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