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 " + entryAid + " 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             for (String aid : service.getAids()) {
    363                 if (!CardEmulation.isValidAid(aid)) {
    364                     Log.e(TAG, "Aid " + aid + " is not valid.");
    365                     continue;
    366                 }
    367                 if (aid.endsWith("*") && !supportsAidPrefixRegistration()) {
    368                     Log.e(TAG, "Prefix AID " + aid + " ignored on device that doesn't support it.");
    369                     continue;
    370                 }
    371                 ServiceAidInfo serviceAidInfo = new ServiceAidInfo();
    372                 serviceAidInfo.aid = aid.toUpperCase();
    373                 serviceAidInfo.service = service;
    374                 serviceAidInfo.category = service.getCategoryForAid(aid);
    375 
    376                 if (mAidServices.containsKey(serviceAidInfo.aid)) {
    377                     final ArrayList<ServiceAidInfo> serviceAidInfos =
    378                             mAidServices.get(serviceAidInfo.aid);
    379                     serviceAidInfos.add(serviceAidInfo);
    380                 } else {
    381                     final ArrayList<ServiceAidInfo> serviceAidInfos =
    382                             new ArrayList<ServiceAidInfo>();
    383                     serviceAidInfos.add(serviceAidInfo);
    384                     mAidServices.put(serviceAidInfo.aid, serviceAidInfos);
    385                 }
    386             }
    387         }
    388     }
    389 
    390     static boolean isPrefix(String aid) {
    391         return aid.endsWith("*");
    392     }
    393 
    394     final class PrefixConflicts {
    395         NavigableMap<String, ArrayList<ServiceAidInfo>> conflictMap;
    396         final ArrayList<ServiceAidInfo> services = new ArrayList<ServiceAidInfo>();
    397         final HashSet<String> aids = new HashSet<String>();
    398     }
    399 
    400     PrefixConflicts findConflictsForPrefixLocked(String prefixAid) {
    401         PrefixConflicts prefixConflicts = new PrefixConflicts();
    402         String plainAid = prefixAid.substring(0, prefixAid.length() - 1); // Cut off "*"
    403         String lastAidWithPrefix = String.format("%-32s", plainAid).replace(' ', 'F');
    404         if (DBG) Log.d(TAG, "Finding AIDs in range [" + plainAid + " - " +
    405                 lastAidWithPrefix + "]");
    406         prefixConflicts.conflictMap =
    407                 mAidServices.subMap(plainAid, true, lastAidWithPrefix, true);
    408         for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry :
    409                 prefixConflicts.conflictMap.entrySet()) {
    410             if (!entry.getKey().equalsIgnoreCase(prefixAid)) {
    411                 if (DBG)
    412                     Log.d(TAG, "AID " + entry.getKey() + " conflicts with prefix; " +
    413                             " adding handling services for conflict resolution.");
    414                 prefixConflicts.services.addAll(entry.getValue());
    415                 prefixConflicts.aids.add(entry.getKey());
    416             }
    417         }
    418         return prefixConflicts;
    419     }
    420 
    421     void generateAidCacheLocked() {
    422         mAidCache.clear();
    423         // Get all exact and prefix AIDs in an ordered list
    424         PriorityQueue<String> aidsToResolve = new PriorityQueue<String>(mAidServices.keySet());
    425 
    426         while (!aidsToResolve.isEmpty()) {
    427             final ArrayList<String> resolvedAids = new ArrayList<String>();
    428 
    429             String aidToResolve = aidsToResolve.peek();
    430             // Because of the lexicographical ordering, all following AIDs either start with the
    431             // same bytes and are longer, or start with different bytes.
    432 
    433             // A special case is if another service registered the same AID as a prefix, in
    434             // which case we want to start with that AID, since it conflicts with this one
    435             if (aidsToResolve.contains(aidToResolve + "*")) {
    436                 aidToResolve = aidToResolve + "*";
    437             }
    438             if (DBG) Log.d(TAG, "generateAidCacheLocked: starting with aid " + aidToResolve);
    439 
    440             if (isPrefix(aidToResolve)) {
    441                 // This AID itself is a prefix; let's consider this prefix as the "root",
    442                 // and all conflicting AIDs as its children.
    443                 // For example, if "A000000003*" is the prefix root,
    444                 // "A000000003", "A00000000301*", "A0000000030102" are all conflicting children AIDs
    445                 final ArrayList<ServiceAidInfo> prefixServices = new ArrayList<ServiceAidInfo>(
    446                         mAidServices.get(aidToResolve));
    447 
    448                 // Find all conflicting children services
    449                 PrefixConflicts prefixConflicts = findConflictsForPrefixLocked(aidToResolve);
    450 
    451                 // Resolve conflicts
    452                 AidResolveInfo resolveInfo = resolvePrefixAidConflictLocked(prefixServices,
    453                         prefixConflicts.services);
    454                 mAidCache.put(aidToResolve, resolveInfo);
    455                 resolvedAids.add(aidToResolve);
    456                 if (resolveInfo.defaultService != null) {
    457                     // This prefix is the default; therefore, AIDs of all conflicting children
    458                     // will no longer be evaluated.
    459                     resolvedAids.addAll(prefixConflicts.aids);
    460                 } else if (resolveInfo.services.size() > 0) {
    461                     // This means we don't have a default for this prefix and all its
    462                     // conflicting children. So, for all conflicting AIDs, just add
    463                     // all handling services without setting a default
    464                     boolean foundChildService = false;
    465                     for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry :
    466                             prefixConflicts.conflictMap.entrySet()) {
    467                         if (!entry.getKey().equalsIgnoreCase(aidToResolve)) {
    468                             if (DBG)
    469                                 Log.d(TAG, "AID " + entry.getKey() + " shared with prefix; " +
    470                                         " adding all handling services.");
    471                             AidResolveInfo childResolveInfo = resolveAidConflictLocked(
    472                                     entry.getValue(), false);
    473                             // Special case: in this case all children AIDs must be routed to the
    474                             // host, so we can ask the user which service is preferred.
    475                             // Since these are all "children" of the prefix, they don't need
    476                             // to be routed, since the prefix will already get routed to the host
    477                             childResolveInfo.mustRoute = false;
    478                             mAidCache.put(entry.getKey(),childResolveInfo);
    479                             resolvedAids.add(entry.getKey());
    480                             foundChildService |= !childResolveInfo.services.isEmpty();
    481                         }
    482                     }
    483                     // Special case: if in the end we didn't add any children services,
    484                     // and the prefix has only one service, make that default
    485                     if (!foundChildService && resolveInfo.services.size() == 1) {
    486                         resolveInfo.defaultService = resolveInfo.services.get(0);
    487                     }
    488                 } else {
    489                     // This prefix is not handled at all; we will evaluate
    490                     // the children separately in next passes.
    491                 }
    492             } else {
    493                 // Exact AID and no other conflicting AID registrations present
    494                 // This is true because aidsToResolve is lexicographically ordered, and
    495                 // so by necessity all other AIDs are different than this AID or longer.
    496                 if (DBG) Log.d(TAG, "Exact AID, resolving.");
    497                 final ArrayList<ServiceAidInfo> conflictingServiceInfos =
    498                         new ArrayList<ServiceAidInfo>(mAidServices.get(aidToResolve));
    499                 mAidCache.put(aidToResolve, resolveAidConflictLocked(conflictingServiceInfos, true));
    500                 resolvedAids.add(aidToResolve);
    501             }
    502 
    503             // Remove the AIDs we resolved from the list of AIDs to resolve
    504             if (DBG) Log.d(TAG, "AIDs: " + resolvedAids + " were resolved.");
    505             aidsToResolve.removeAll(resolvedAids);
    506             resolvedAids.clear();
    507         }
    508 
    509         updateRoutingLocked();
    510     }
    511 
    512     void updateRoutingLocked() {
    513         if (!mNfcEnabled) {
    514             if (DBG) Log.d(TAG, "Not updating routing table because NFC is off.");
    515             return;
    516         }
    517         final HashMap<String, Boolean> routingEntries = Maps.newHashMap();
    518         // For each AID, find interested services
    519         for (Map.Entry<String, AidResolveInfo> aidEntry:
    520                 mAidCache.entrySet()) {
    521             String aid = aidEntry.getKey();
    522             AidResolveInfo resolveInfo = aidEntry.getValue();
    523             if (!resolveInfo.mustRoute) {
    524                 if (DBG) Log.d(TAG, "Not routing AID " + aid + " on request.");
    525                 continue;
    526             }
    527             if (resolveInfo.services.size() == 0) {
    528                 // No interested services
    529             } else if (resolveInfo.defaultService != null) {
    530                 // There is a default service set, route to where that service resides -
    531                 // either on the host (HCE) or on an SE.
    532                 routingEntries.put(aid, resolveInfo.defaultService.isOnHost());
    533             } else if (resolveInfo.services.size() == 1) {
    534                 // Only one service, but not the default, must route to host
    535                 // to ask the user to choose one.
    536                 routingEntries.put(aid, true);
    537             } else if (resolveInfo.services.size() > 1) {
    538                 // Multiple services, need to route to host to ask
    539                 routingEntries.put(aid, true);
    540             }
    541         }
    542         mRoutingManager.configureRouting(routingEntries);
    543     }
    544 
    545     public void onServicesUpdated(int userId, List<ApduServiceInfo> services) {
    546         if (DBG) Log.d(TAG, "onServicesUpdated");
    547         synchronized (mLock) {
    548             if (ActivityManager.getCurrentUser() == userId) {
    549                 // Rebuild our internal data-structures
    550                 generateServiceMapLocked(services);
    551                 generateAidCacheLocked();
    552             } else {
    553                 if (DBG) Log.d(TAG, "Ignoring update because it's not for the current user.");
    554             }
    555         }
    556     }
    557 
    558     public void onPreferredPaymentServiceChanged(ComponentName service) {
    559         if (DBG) Log.d(TAG, "Preferred payment service changed.");
    560        synchronized (mLock) {
    561            mPreferredPaymentService = service;
    562            generateAidCacheLocked();
    563        }
    564     }
    565 
    566     public void onPreferredForegroundServiceChanged(ComponentName service) {
    567         if (DBG) Log.d(TAG, "Preferred foreground service changed.");
    568         synchronized (mLock) {
    569             mPreferredForegroundService = service;
    570             generateAidCacheLocked();
    571         }
    572     }
    573 
    574     public void onNfcDisabled() {
    575         synchronized (mLock) {
    576             mNfcEnabled = false;
    577         }
    578         mRoutingManager.onNfccRoutingTableCleared();
    579     }
    580 
    581     public void onNfcEnabled() {
    582         synchronized (mLock) {
    583             mNfcEnabled = true;
    584             updateRoutingLocked();
    585         }
    586     }
    587 
    588     String dumpEntry(Map.Entry<String, AidResolveInfo> entry) {
    589         StringBuilder sb = new StringBuilder();
    590         String category = entry.getValue().category;
    591         ApduServiceInfo defaultServiceInfo = entry.getValue().defaultService;
    592         sb.append("    \"" + entry.getKey() + "\" (category: " + category + ")\n");
    593         ComponentName defaultComponent = defaultServiceInfo != null ?
    594                 defaultServiceInfo.getComponent() : null;
    595 
    596         for (ApduServiceInfo serviceInfo : entry.getValue().services) {
    597             sb.append("        ");
    598             if (serviceInfo.getComponent().equals(defaultComponent)) {
    599                 sb.append("*DEFAULT* ");
    600             }
    601             sb.append(serviceInfo.getComponent() +
    602                     " (Description: " + serviceInfo.getDescription() + ")\n");
    603         }
    604         return sb.toString();
    605     }
    606 
    607     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    608         pw.println("    AID cache entries: ");
    609         for (Map.Entry<String, AidResolveInfo> entry : mAidCache.entrySet()) {
    610             pw.println(dumpEntry(entry));
    611         }
    612         pw.println("    Service preferred by foreground app: " + mPreferredForegroundService);
    613         pw.println("    Preferred payment service: " + mPreferredPaymentService);
    614         pw.println("");
    615         mRoutingManager.dump(fd, pw, args);
    616         pw.println("");
    617     }
    618 }
    619