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