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