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