Home | History | Annotate | Download | only in cardemulation
      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
     65         public String toString() {
     66             return "ServiceAidInfo{" +
     67                     "service=" + service.getComponent() +
     68                     ", aid='" + aid + '\'' +
     69                     ", category='" + category + '\'' +
     70                     '}';
     71         }
     72 
     73         @Override
     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
     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
    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 
    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 
    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 
    191     public boolean supportsAidPrefixRegistration() {
    192         return mSupportsPrefixes;
    193     }
    194 
    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      */
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    590     public void onNfcDisabled() {
    591         synchronized (mLock) {
    592             mNfcEnabled = false;
    593         }
    594         mRoutingManager.onNfccRoutingTableCleared();
    595     }
    596 
    597     public void onNfcEnabled() {
    598         synchronized (mLock) {
    599             mNfcEnabled = true;
    600             updateRoutingLocked();
    601         }
    602     }
    603 
    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 
    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