1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.nfc.cardemulation;
18 
19 import android.app.ActivityManager;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.nfc.cardemulation.ApduServiceInfo;
23 import android.nfc.cardemulation.CardEmulation;
24 import android.util.Log;
25 
26 import com.google.android.collect.Maps;
27 
28 import java.io.FileDescriptor;
29 import java.io.PrintWriter;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.NavigableMap;
37 import java.util.PriorityQueue;
38 import java.util.TreeMap;
39 
40 public class RegisteredAidCache {
41     static final String TAG = "RegisteredAidCache";
42 
43     static final boolean DBG = false;
44 
45     // mAidServices maps AIDs to services that have registered them.
46     // It's a TreeMap in order to be able to quickly select subsets
47     // of AIDs that conflict with each other.
48     final TreeMap<String, ArrayList<ServiceAidInfo>> mAidServices =
49             new TreeMap<String, ArrayList<ServiceAidInfo>>();
50 
51     // mAidCache is a lookup table for quickly mapping an exact or prefix AID to one or
52     // more handling services. It differs from mAidServices in the sense that it
53     // has already accounted for defaults, and hence its return value
54     // is authoritative for the current set of services and defaults.
55     // It is only valid for the current user.
56     final TreeMap<String, AidResolveInfo> mAidCache = new TreeMap<String, AidResolveInfo>();
57 
58     // Represents a single AID registration of a service
59     final class ServiceAidInfo {
60         ApduServiceInfo service;
61         String aid;
62         String category;
63 
64         @Override
toString()65         public String toString() {
66             return "ServiceAidInfo{" +
67                     "service=" + service.getComponent() +
68                     ", aid='" + aid + '\'' +
69                     ", category='" + category + '\'' +
70                     '}';
71         }
72 
73         @Override
equals(Object o)74         public boolean equals(Object o) {
75             if (this == o) return true;
76             if (o == null || getClass() != o.getClass()) return false;
77 
78             ServiceAidInfo that = (ServiceAidInfo) o;
79 
80             if (!aid.equals(that.aid)) return false;
81             if (!category.equals(that.category)) return false;
82             if (!service.equals(that.service)) return false;
83 
84             return true;
85         }
86 
87         @Override
hashCode()88         public int hashCode() {
89             int result = service.hashCode();
90             result = 31 * result + aid.hashCode();
91             result = 31 * result + category.hashCode();
92             return result;
93         }
94     }
95 
96     // Represents a list of services, an optional default and a category that
97     // an AID was resolved to.
98     final class AidResolveInfo {
99         List<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
100         ApduServiceInfo defaultService = null;
101         String category = null;
102         boolean mustRoute = true; // Whether this AID should be routed at all
103 
104         @Override
toString()105         public String toString() {
106             return "AidResolveInfo{" +
107                     "services=" + services +
108                     ", defaultService=" + defaultService +
109                     ", category='" + category + '\'' +
110                     ", mustRoute=" + mustRoute +
111                     '}';
112         }
113     }
114 
115     final AidResolveInfo EMPTY_RESOLVE_INFO = new AidResolveInfo();
116 
117     final Context mContext;
118     final AidRoutingManager mRoutingManager;
119 
120     final Object mLock = new Object();
121 
122     ComponentName mPreferredPaymentService;
123     ComponentName mPreferredForegroundService;
124 
125     boolean mNfcEnabled = false;
126     boolean mSupportsPrefixes = false;
127 
RegisteredAidCache(Context context)128     public RegisteredAidCache(Context context) {
129         mContext = context;
130         mRoutingManager = new AidRoutingManager();
131         mPreferredPaymentService = null;
132         mPreferredForegroundService = null;
133         mSupportsPrefixes = mRoutingManager.supportsAidPrefixRouting();
134         if (mSupportsPrefixes) {
135             if (DBG) Log.d(TAG, "Controller supports AID prefix routing");
136         }
137     }
138 
resolveAid(String aid)139     public AidResolveInfo resolveAid(String aid) {
140         synchronized (mLock) {
141             if (DBG) Log.d(TAG, "resolveAid: resolving AID " + aid);
142             if (aid.length() < 10) {
143                 Log.e(TAG, "AID selected with fewer than 5 bytes.");
144                 return EMPTY_RESOLVE_INFO;
145             }
146             AidResolveInfo resolveInfo = new AidResolveInfo();
147             if (mSupportsPrefixes) {
148                 // Our AID cache may contain prefixes which also match this AID,
149                 // so we must find all potential prefixes and merge the ResolveInfo
150                 // of those prefixes plus any exact match in a single result.
151                 String shortestAidMatch = aid.substring(0, 10); // Minimum AID length is 5 bytes
152                 String longestAidMatch = aid + "*"; // Longest potential matching AID
153 
154                 if (DBG) Log.d(TAG, "Finding AID registrations in range [" + shortestAidMatch +
155                         " - " + longestAidMatch + "]");
156                 NavigableMap<String, AidResolveInfo> matchingAids =
157                         mAidCache.subMap(shortestAidMatch, true, longestAidMatch, true);
158 
159                 resolveInfo.category = CardEmulation.CATEGORY_OTHER;
160                 for (Map.Entry<String, AidResolveInfo> entry : matchingAids.entrySet()) {
161                     boolean isPrefix = isPrefix(entry.getKey());
162                     String entryAid = isPrefix ? entry.getKey().substring(0,
163                             entry.getKey().length() - 1) : entry.getKey(); // Cut off '*' if prefix
164                     if (entryAid.equalsIgnoreCase(aid) || (isPrefix && aid.startsWith(entryAid))) {
165                         if (DBG) Log.d(TAG, "resolveAid: AID " + entry.getKey() + " matches.");
166                         AidResolveInfo entryResolveInfo = entry.getValue();
167                         if (entryResolveInfo.defaultService != null) {
168                             if (resolveInfo.defaultService != null) {
169                                 // This shouldn't happen; for every prefix we have only one
170                                 // default service.
171                                 Log.e(TAG, "Different defaults for conflicting AIDs!");
172                             }
173                             resolveInfo.defaultService = entryResolveInfo.defaultService;
174                             resolveInfo.category = entryResolveInfo.category;
175                         }
176                         for (ApduServiceInfo serviceInfo : entryResolveInfo.services) {
177                             if (!resolveInfo.services.contains(serviceInfo)) {
178                                 resolveInfo.services.add(serviceInfo);
179                             }
180                         }
181                     }
182                 }
183             } else {
184                 resolveInfo = mAidCache.get(aid);
185             }
186             if (DBG) Log.d(TAG, "Resolved to: " + resolveInfo);
187             return resolveInfo;
188         }
189     }
190 
supportsAidPrefixRegistration()191     public boolean supportsAidPrefixRegistration() {
192         return mSupportsPrefixes;
193     }
194 
isDefaultServiceForAid(int userId, ComponentName service, String aid)195     public boolean isDefaultServiceForAid(int userId, ComponentName service, String aid) {
196         AidResolveInfo resolveInfo = resolveAid(aid);
197         if (resolveInfo == null || resolveInfo.services == null ||
198                 resolveInfo.services.size() == 0) {
199             return false;
200         }
201 
202         if (resolveInfo.defaultService != null) {
203             return service.equals(resolveInfo.defaultService.getComponent());
204         } else if (resolveInfo.services.size() == 1) {
205             return service.equals(resolveInfo.services.get(0).getComponent());
206         } else {
207             // More than one service, not the default
208             return false;
209         }
210     }
211 
212     /**
213      * Resolves a conflict between multiple services handling the same
214      * AIDs. Note that the AID itself is not an input to the decision
215      * process - the algorithm just looks at the competing services
216      * and what preferences the user has indicated. In short, it works like
217      * this:
218      *
219      * 1) If there is a preferred foreground service, that service wins
220      * 2) Else, if there is a preferred payment service, that service wins
221      * 3) Else, if there is no winner, and all conflicting services will be
222      *    in the list of resolved services.
223      */
resolveAidConflictLocked(Collection<ServiceAidInfo> conflictingServices, boolean makeSingleServiceDefault)224      AidResolveInfo resolveAidConflictLocked(Collection<ServiceAidInfo> conflictingServices,
225                                              boolean makeSingleServiceDefault) {
226         if (conflictingServices == null || conflictingServices.size() == 0) {
227             Log.e(TAG, "resolveAidConflict: No services passed in.");
228             return null;
229         }
230         AidResolveInfo resolveInfo = new AidResolveInfo();
231         resolveInfo.category = CardEmulation.CATEGORY_OTHER;
232 
233         ApduServiceInfo matchedForeground = null;
234         ApduServiceInfo matchedPayment = null;
235         for (ServiceAidInfo serviceAidInfo : conflictingServices) {
236             boolean serviceClaimsPaymentAid =
237                     CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category);
238             if (serviceAidInfo.service.getComponent().equals(mPreferredForegroundService)) {
239                 resolveInfo.services.add(serviceAidInfo.service);
240                 if (serviceClaimsPaymentAid) {
241                     resolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
242                 }
243                 matchedForeground = serviceAidInfo.service;
244             } else if (serviceAidInfo.service.getComponent().equals(mPreferredPaymentService) &&
245                     serviceClaimsPaymentAid) {
246                 resolveInfo.services.add(serviceAidInfo.service);
247                 resolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
248                 matchedPayment = serviceAidInfo.service;
249             } else {
250                 if (serviceClaimsPaymentAid) {
251                     // If this service claims it's a payment AID, don't route it,
252                     // because it's not the default. Otherwise, add it to the list
253                     // but not as default.
254                     if (DBG) Log.d(TAG, "resolveAidLocked: (Ignoring handling service " +
255                             serviceAidInfo.service.getComponent() +
256                             " because it's not the payment default.)");
257                 } else {
258                     resolveInfo.services.add(serviceAidInfo.service);
259                 }
260             }
261         }
262         if (matchedForeground != null) {
263             // 1st priority: if the foreground app prefers a service,
264             // and that service asks for the AID, that service gets it
265             if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to foreground preferred " +
266                     matchedForeground);
267             resolveInfo.defaultService = matchedForeground;
268         } else if (matchedPayment != null) {
269             // 2nd priority: if there is a preferred payment service,
270             // and that service claims this as a payment AID, that service gets it
271             if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to payment default " +
272                     "default " + matchedPayment);
273             resolveInfo.defaultService = matchedPayment;
274         } else {
275             if (resolveInfo.services.size() == 1 && makeSingleServiceDefault) {
276                 if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: making single handling service " +
277                         resolveInfo.services.get(0).getComponent() + " default.");
278                 resolveInfo.defaultService = resolveInfo.services.get(0);
279             } else {
280                 // Nothing to do, all services already in list
281                 if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to all matching services");
282             }
283         }
284         return resolveInfo;
285     }
286 
287     class DefaultServiceInfo {
288         ServiceAidInfo paymentDefault;
289         ServiceAidInfo foregroundDefault;
290     }
291 
findDefaultServices(ArrayList<ServiceAidInfo> serviceAidInfos)292     DefaultServiceInfo findDefaultServices(ArrayList<ServiceAidInfo> serviceAidInfos) {
293         DefaultServiceInfo defaultServiceInfo = new DefaultServiceInfo();
294 
295         for (ServiceAidInfo serviceAidInfo : serviceAidInfos) {
296             boolean serviceClaimsPaymentAid =
297                     CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category);
298             if (serviceAidInfo.service.getComponent().equals(mPreferredForegroundService)) {
299                 defaultServiceInfo.foregroundDefault = serviceAidInfo;
300             } else if (serviceAidInfo.service.getComponent().equals(mPreferredPaymentService) &&
301                     serviceClaimsPaymentAid) {
302                 defaultServiceInfo.paymentDefault = serviceAidInfo;
303             }
304         }
305         return defaultServiceInfo;
306     }
307 
resolvePrefixAidConflictLocked(ArrayList<ServiceAidInfo> prefixServices, ArrayList<ServiceAidInfo> conflictingServices)308     AidResolveInfo resolvePrefixAidConflictLocked(ArrayList<ServiceAidInfo> prefixServices,
309                                                   ArrayList<ServiceAidInfo> conflictingServices) {
310         // Find defaults among the prefix services themselves
311         DefaultServiceInfo prefixDefaultInfo = findDefaultServices(prefixServices);
312 
313         // Find any defaults among the children
314         DefaultServiceInfo conflictingDefaultInfo = findDefaultServices(conflictingServices);
315 
316         // Three conditions under which the prefix root AID gets to be the default
317         // 1. A service registering the prefix root AID is the current foreground preferred
318         // 2. A service registering the prefix root AID is the current tap & pay default AND
319         //    no child is the current foreground preferred
320         // 3. There is only one service for the prefix root AID, and there are no children
321         if (prefixDefaultInfo.foregroundDefault != null) {
322             if (DBG) Log.d(TAG, "Prefix AID service " +
323                     prefixDefaultInfo.foregroundDefault.service.getComponent() + " has foreground" +
324                     " preference, ignoring conflicting AIDs.");
325             // Foreground default trumps any conflicting services, treat as normal AID conflict
326             // and ignore children
327             return resolveAidConflictLocked(prefixServices, true);
328         } else if (prefixDefaultInfo.paymentDefault != null) {
329             // Check if any of the conflicting services is foreground default
330             if (conflictingDefaultInfo.foregroundDefault != null) {
331                 // Conflicting AID registration is in foreground, trumps prefix tap&pay default
332                 if (DBG) Log.d(TAG, "One of the conflicting AID registrations is foreground " +
333                         "preferred, ignoring prefix.");
334                 return EMPTY_RESOLVE_INFO;
335             } else {
336                 // Prefix service is tap&pay default, treat as normal AID conflict for just prefix
337                 if (DBG) Log.d(TAG, "Prefix AID service " +
338                         prefixDefaultInfo.paymentDefault.service.getComponent() + " is payment" +
339                         " default, ignoring conflicting AIDs.");
340                 return resolveAidConflictLocked(prefixServices, true);
341             }
342         } else {
343             if (conflictingDefaultInfo.foregroundDefault != null ||
344                     conflictingDefaultInfo.paymentDefault != null) {
345                 if (DBG) Log.d(TAG, "One of the conflicting AID registrations is either payment " +
346                         "default or foreground preferred, ignoring prefix.");
347                 return EMPTY_RESOLVE_INFO;
348             } else {
349                 // No children that are preferred; add all services of the root
350                 // make single service default if no children are present
351                 if (DBG) Log.d(TAG, "No service has preference, adding all.");
352                 return resolveAidConflictLocked(prefixServices, conflictingServices.isEmpty());
353             }
354         }
355     }
356 
generateServiceMapLocked(List<ApduServiceInfo> services)357     void generateServiceMapLocked(List<ApduServiceInfo> services) {
358         // Easiest is to just build the entire tree again
359         mAidServices.clear();
360         for (ApduServiceInfo service : services) {
361             if (DBG) Log.d(TAG, "generateServiceMap component: " + service.getComponent());
362             List<String> prefixAids = service.getPrefixAids();
363             for (String aid : service.getAids()) {
364                 if (!CardEmulation.isValidAid(aid)) {
365                     Log.e(TAG, "Aid " + aid + " is not valid.");
366                     continue;
367                 }
368                 if (aid.endsWith("*") && !supportsAidPrefixRegistration()) {
369                     Log.e(TAG, "Prefix AID " + aid + " ignored on device that doesn't support it.");
370                     continue;
371                 } else if (supportsAidPrefixRegistration() && prefixAids.size() > 0 && !isPrefix(aid)) {
372                     // Check if we already have an overlapping prefix registered for this AID
373                     boolean foundPrefix = false;
374                     for (String prefixAid : prefixAids) {
375                         String prefix = prefixAid.substring(0, prefixAid.length() - 1);
376                         if (aid.startsWith(prefix)) {
377                             Log.e(TAG, "Ignoring exact AID " + aid + " because prefix AID " + prefixAid +
378                                     " is already registered");
379                             foundPrefix = true;
380                             break;
381                         }
382                     }
383                     if (foundPrefix) {
384                         continue;
385                     }
386                 }
387                 ServiceAidInfo serviceAidInfo = new ServiceAidInfo();
388                 serviceAidInfo.aid = aid.toUpperCase();
389                 serviceAidInfo.service = service;
390                 serviceAidInfo.category = service.getCategoryForAid(aid);
391 
392                 if (mAidServices.containsKey(serviceAidInfo.aid)) {
393                     final ArrayList<ServiceAidInfo> serviceAidInfos =
394                             mAidServices.get(serviceAidInfo.aid);
395                     serviceAidInfos.add(serviceAidInfo);
396                 } else {
397                     final ArrayList<ServiceAidInfo> serviceAidInfos =
398                             new ArrayList<ServiceAidInfo>();
399                     serviceAidInfos.add(serviceAidInfo);
400                     mAidServices.put(serviceAidInfo.aid, serviceAidInfos);
401                 }
402             }
403         }
404     }
405 
isPrefix(String aid)406     static boolean isPrefix(String aid) {
407         return aid.endsWith("*");
408     }
409 
410     final class PrefixConflicts {
411         NavigableMap<String, ArrayList<ServiceAidInfo>> conflictMap;
412         final ArrayList<ServiceAidInfo> services = new ArrayList<ServiceAidInfo>();
413         final HashSet<String> aids = new HashSet<String>();
414     }
415 
findConflictsForPrefixLocked(String prefixAid)416     PrefixConflicts findConflictsForPrefixLocked(String prefixAid) {
417         PrefixConflicts prefixConflicts = new PrefixConflicts();
418         String plainAid = prefixAid.substring(0, prefixAid.length() - 1); // Cut off "*"
419         String lastAidWithPrefix = String.format("%-32s", plainAid).replace(' ', 'F');
420         if (DBG) Log.d(TAG, "Finding AIDs in range [" + plainAid + " - " +
421                 lastAidWithPrefix + "]");
422         prefixConflicts.conflictMap =
423                 mAidServices.subMap(plainAid, true, lastAidWithPrefix, true);
424         for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry :
425                 prefixConflicts.conflictMap.entrySet()) {
426             if (!entry.getKey().equalsIgnoreCase(prefixAid)) {
427                 if (DBG)
428                     Log.d(TAG, "AID " + entry.getKey() + " conflicts with prefix; " +
429                             " adding handling services for conflict resolution.");
430                 prefixConflicts.services.addAll(entry.getValue());
431                 prefixConflicts.aids.add(entry.getKey());
432             }
433         }
434         return prefixConflicts;
435     }
436 
generateAidCacheLocked()437     void generateAidCacheLocked() {
438         mAidCache.clear();
439         // Get all exact and prefix AIDs in an ordered list
440         PriorityQueue<String> aidsToResolve = new PriorityQueue<String>(mAidServices.keySet());
441 
442         while (!aidsToResolve.isEmpty()) {
443             final ArrayList<String> resolvedAids = new ArrayList<String>();
444 
445             String aidToResolve = aidsToResolve.peek();
446             // Because of the lexicographical ordering, all following AIDs either start with the
447             // same bytes and are longer, or start with different bytes.
448 
449             // A special case is if another service registered the same AID as a prefix, in
450             // which case we want to start with that AID, since it conflicts with this one
451             if (aidsToResolve.contains(aidToResolve + "*")) {
452                 aidToResolve = aidToResolve + "*";
453             }
454             if (DBG) Log.d(TAG, "generateAidCacheLocked: starting with aid " + aidToResolve);
455 
456             if (isPrefix(aidToResolve)) {
457                 // This AID itself is a prefix; let's consider this prefix as the "root",
458                 // and all conflicting AIDs as its children.
459                 // For example, if "A000000003*" is the prefix root,
460                 // "A000000003", "A00000000301*", "A0000000030102" are all conflicting children AIDs
461                 final ArrayList<ServiceAidInfo> prefixServices = new ArrayList<ServiceAidInfo>(
462                         mAidServices.get(aidToResolve));
463 
464                 // Find all conflicting children services
465                 PrefixConflicts prefixConflicts = findConflictsForPrefixLocked(aidToResolve);
466 
467                 // Resolve conflicts
468                 AidResolveInfo resolveInfo = resolvePrefixAidConflictLocked(prefixServices,
469                         prefixConflicts.services);
470                 mAidCache.put(aidToResolve, resolveInfo);
471                 resolvedAids.add(aidToResolve);
472                 if (resolveInfo.defaultService != null) {
473                     // This prefix is the default; therefore, AIDs of all conflicting children
474                     // will no longer be evaluated.
475                     resolvedAids.addAll(prefixConflicts.aids);
476                 } else if (resolveInfo.services.size() > 0) {
477                     // This means we don't have a default for this prefix and all its
478                     // conflicting children. So, for all conflicting AIDs, just add
479                     // all handling services without setting a default
480                     boolean foundChildService = false;
481                     for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry :
482                             prefixConflicts.conflictMap.entrySet()) {
483                         if (!entry.getKey().equalsIgnoreCase(aidToResolve)) {
484                             if (DBG)
485                                 Log.d(TAG, "AID " + entry.getKey() + " shared with prefix; " +
486                                         " adding all handling services.");
487                             AidResolveInfo childResolveInfo = resolveAidConflictLocked(
488                                     entry.getValue(), false);
489                             // Special case: in this case all children AIDs must be routed to the
490                             // host, so we can ask the user which service is preferred.
491                             // Since these are all "children" of the prefix, they don't need
492                             // to be routed, since the prefix will already get routed to the host
493                             childResolveInfo.mustRoute = false;
494                             mAidCache.put(entry.getKey(),childResolveInfo);
495                             resolvedAids.add(entry.getKey());
496                             foundChildService |= !childResolveInfo.services.isEmpty();
497                         }
498                     }
499                     // Special case: if in the end we didn't add any children services,
500                     // and the prefix has only one service, make that default
501                     if (!foundChildService && resolveInfo.services.size() == 1) {
502                         resolveInfo.defaultService = resolveInfo.services.get(0);
503                     }
504                 } else {
505                     // This prefix is not handled at all; we will evaluate
506                     // the children separately in next passes.
507                 }
508             } else {
509                 // Exact AID and no other conflicting AID registrations present
510                 // This is true because aidsToResolve is lexicographically ordered, and
511                 // so by necessity all other AIDs are different than this AID or longer.
512                 if (DBG) Log.d(TAG, "Exact AID, resolving.");
513                 final ArrayList<ServiceAidInfo> conflictingServiceInfos =
514                         new ArrayList<ServiceAidInfo>(mAidServices.get(aidToResolve));
515                 mAidCache.put(aidToResolve, resolveAidConflictLocked(conflictingServiceInfos, true));
516                 resolvedAids.add(aidToResolve);
517             }
518 
519             // Remove the AIDs we resolved from the list of AIDs to resolve
520             if (DBG) Log.d(TAG, "AIDs: " + resolvedAids + " were resolved.");
521             aidsToResolve.removeAll(resolvedAids);
522             resolvedAids.clear();
523         }
524 
525         updateRoutingLocked();
526     }
527 
updateRoutingLocked()528     void updateRoutingLocked() {
529         if (!mNfcEnabled) {
530             if (DBG) Log.d(TAG, "Not updating routing table because NFC is off.");
531             return;
532         }
533         final HashMap<String, Boolean> routingEntries = Maps.newHashMap();
534         // For each AID, find interested services
535         for (Map.Entry<String, AidResolveInfo> aidEntry:
536                 mAidCache.entrySet()) {
537             String aid = aidEntry.getKey();
538             AidResolveInfo resolveInfo = aidEntry.getValue();
539             if (!resolveInfo.mustRoute) {
540                 if (DBG) Log.d(TAG, "Not routing AID " + aid + " on request.");
541                 continue;
542             }
543             if (resolveInfo.services.size() == 0) {
544                 // No interested services
545             } else if (resolveInfo.defaultService != null) {
546                 // There is a default service set, route to where that service resides -
547                 // either on the host (HCE) or on an SE.
548                 routingEntries.put(aid, resolveInfo.defaultService.isOnHost());
549             } else if (resolveInfo.services.size() == 1) {
550                 // Only one service, but not the default, must route to host
551                 // to ask the user to choose one.
552                 routingEntries.put(aid, true);
553             } else if (resolveInfo.services.size() > 1) {
554                 // Multiple services, need to route to host to ask
555                 routingEntries.put(aid, true);
556             }
557         }
558         mRoutingManager.configureRouting(routingEntries);
559     }
560 
onServicesUpdated(int userId, List<ApduServiceInfo> services)561     public void onServicesUpdated(int userId, List<ApduServiceInfo> services) {
562         if (DBG) Log.d(TAG, "onServicesUpdated");
563         synchronized (mLock) {
564             if (ActivityManager.getCurrentUser() == userId) {
565                 // Rebuild our internal data-structures
566                 generateServiceMapLocked(services);
567                 generateAidCacheLocked();
568             } else {
569                 if (DBG) Log.d(TAG, "Ignoring update because it's not for the current user.");
570             }
571         }
572     }
573 
onPreferredPaymentServiceChanged(ComponentName service)574     public void onPreferredPaymentServiceChanged(ComponentName service) {
575         if (DBG) Log.d(TAG, "Preferred payment service changed.");
576        synchronized (mLock) {
577            mPreferredPaymentService = service;
578            generateAidCacheLocked();
579        }
580     }
581 
onPreferredForegroundServiceChanged(ComponentName service)582     public void onPreferredForegroundServiceChanged(ComponentName service) {
583         if (DBG) Log.d(TAG, "Preferred foreground service changed.");
584         synchronized (mLock) {
585             mPreferredForegroundService = service;
586             generateAidCacheLocked();
587         }
588     }
589 
onNfcDisabled()590     public void onNfcDisabled() {
591         synchronized (mLock) {
592             mNfcEnabled = false;
593         }
594         mRoutingManager.onNfccRoutingTableCleared();
595     }
596 
onNfcEnabled()597     public void onNfcEnabled() {
598         synchronized (mLock) {
599             mNfcEnabled = true;
600             updateRoutingLocked();
601         }
602     }
603 
dumpEntry(Map.Entry<String, AidResolveInfo> entry)604     String dumpEntry(Map.Entry<String, AidResolveInfo> entry) {
605         StringBuilder sb = new StringBuilder();
606         String category = entry.getValue().category;
607         ApduServiceInfo defaultServiceInfo = entry.getValue().defaultService;
608         sb.append("    \"" + entry.getKey() + "\" (category: " + category + ")\n");
609         ComponentName defaultComponent = defaultServiceInfo != null ?
610                 defaultServiceInfo.getComponent() : null;
611 
612         for (ApduServiceInfo serviceInfo : entry.getValue().services) {
613             sb.append("        ");
614             if (serviceInfo.getComponent().equals(defaultComponent)) {
615                 sb.append("*DEFAULT* ");
616             }
617             sb.append(serviceInfo.getComponent() +
618                     " (Description: " + serviceInfo.getDescription() + ")\n");
619         }
620         return sb.toString();
621     }
622 
dump(FileDescriptor fd, PrintWriter pw, String[] args)623     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
624         pw.println("    AID cache entries: ");
625         for (Map.Entry<String, AidResolveInfo> entry : mAidCache.entrySet()) {
626             pw.println(dumpEntry(entry));
627         }
628         pw.println("    Service preferred by foreground app: " + mPreferredForegroundService);
629         pw.println("    Preferred payment service: " + mPreferredPaymentService);
630         pw.println("");
631         mRoutingManager.dump(fd, pw, args);
632         pw.println("");
633     }
634 }
635