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 import java.util.Collections;
     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     static final int AID_ROUTE_QUAL_SUBSET = 0x20;
     46     static final int AID_ROUTE_QUAL_PREFIX = 0x10;
     47 
     48     // mAidServices maps AIDs to services that have registered them.
     49     // It's a TreeMap in order to be able to quickly select subsets
     50     // of AIDs that conflict with each other.
     51     final TreeMap<String, ArrayList<ServiceAidInfo>> mAidServices =
     52             new TreeMap<String, ArrayList<ServiceAidInfo>>();
     53 
     54     // mAidCache is a lookup table for quickly mapping an exact or prefix or subset AID
     55     // to one or more handling services. It differs from mAidServices in the sense that it
     56     // has already accounted for defaults, and hence its return value
     57     // is authoritative for the current set of services and defaults.
     58     // It is only valid for the current user.
     59     final TreeMap<String, AidResolveInfo> mAidCache = new TreeMap<String, AidResolveInfo>();
     60 
     61     // Represents a single AID registration of a service
     62     final class ServiceAidInfo {
     63         ApduServiceInfo service;
     64         String aid;
     65         String category;
     66 
     67         @Override
     68         public String toString() {
     69             return "ServiceAidInfo{" +
     70                     "service=" + service.getComponent() +
     71                     ", aid='" + aid + '\'' +
     72                     ", category='" + category + '\'' +
     73                     '}';
     74         }
     75 
     76         @Override
     77         public boolean equals(Object o) {
     78             if (this == o) return true;
     79             if (o == null || getClass() != o.getClass()) return false;
     80 
     81             ServiceAidInfo that = (ServiceAidInfo) o;
     82 
     83             if (!aid.equals(that.aid)) return false;
     84             if (!category.equals(that.category)) return false;
     85             if (!service.equals(that.service)) return false;
     86 
     87             return true;
     88         }
     89 
     90         @Override
     91         public int hashCode() {
     92             int result = service.hashCode();
     93             result = 31 * result + aid.hashCode();
     94             result = 31 * result + category.hashCode();
     95             return result;
     96         }
     97     }
     98 
     99     // Represents a list of services, an optional default and a category that
    100     // an AID was resolved to.
    101     final class AidResolveInfo {
    102         List<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
    103         ApduServiceInfo defaultService = null;
    104         String category = null;
    105         boolean mustRoute = true; // Whether this AID should be routed at all
    106         ReslovedPrefixConflictAid prefixInfo = null;
    107         @Override
    108         public String toString() {
    109             return "AidResolveInfo{" +
    110                     "services=" + services +
    111                     ", defaultService=" + defaultService +
    112                     ", category='" + category + '\'' +
    113                     ", mustRoute=" + mustRoute +
    114                     '}';
    115         }
    116     }
    117 
    118     final AidResolveInfo EMPTY_RESOLVE_INFO = new AidResolveInfo();
    119 
    120     final Context mContext;
    121     final AidRoutingManager mRoutingManager;
    122 
    123     final Object mLock = new Object();
    124 
    125     ComponentName mPreferredPaymentService;
    126     ComponentName mPreferredForegroundService;
    127 
    128     boolean mNfcEnabled = false;
    129     boolean mSupportsPrefixes = false;
    130     boolean mSupportsSubset = false;
    131 
    132     public RegisteredAidCache(Context context) {
    133         mContext = context;
    134         mRoutingManager = new AidRoutingManager();
    135         mPreferredPaymentService = null;
    136         mPreferredForegroundService = null;
    137         mSupportsPrefixes = mRoutingManager.supportsAidPrefixRouting();
    138         mSupportsSubset   = mRoutingManager.supportsAidSubsetRouting();
    139         if (mSupportsPrefixes) {
    140             if (DBG) Log.d(TAG, "Controller supports AID prefix routing");
    141         }
    142         if (mSupportsSubset) {
    143             if (DBG) Log.d(TAG, "Controller supports AID subset routing");
    144         }
    145     }
    146 
    147     public AidResolveInfo resolveAid(String aid) {
    148         synchronized (mLock) {
    149             if (DBG) Log.d(TAG, "resolveAid: resolving AID " + aid);
    150             if (aid.length() < 10) {
    151                 Log.e(TAG, "AID selected with fewer than 5 bytes.");
    152                 return EMPTY_RESOLVE_INFO;
    153             }
    154             AidResolveInfo resolveInfo = new AidResolveInfo();
    155             if (mSupportsPrefixes || mSupportsSubset) {
    156                 // Our AID cache may contain prefixes/subset which also match this AID,
    157                 // so we must find all potential prefixes or suffixes and merge the ResolveInfo
    158                 // of those prefixes plus any exact match in a single result.
    159                 String shortestAidMatch = aid.substring(0, 10); // Minimum AID length is 5 bytes
    160                 String longestAidMatch = String.format("%-32s", aid).replace(' ', 'F');
    161 
    162 
    163                 if (DBG) Log.d(TAG, "Finding AID registrations in range [" + shortestAidMatch +
    164                         " - " + longestAidMatch + "]");
    165                 NavigableMap<String, AidResolveInfo> matchingAids =
    166                         mAidCache.subMap(shortestAidMatch, true, longestAidMatch, true);
    167 
    168                 resolveInfo.category = CardEmulation.CATEGORY_OTHER;
    169                 for (Map.Entry<String, AidResolveInfo> entry : matchingAids.entrySet()) {
    170                     boolean isPrefix = isPrefix(entry.getKey());
    171                     boolean isSubset = isSubset(entry.getKey());
    172                     String entryAid = (isPrefix || isSubset) ? entry.getKey().substring(0,
    173                             entry.getKey().length() - 1):entry.getKey(); // Cut off '*' if prefix
    174                     if (entryAid.equalsIgnoreCase(aid) || (isPrefix && aid.startsWith(entryAid))
    175                             || (isSubset && entryAid.startsWith(aid))) {
    176                         if (DBG) Log.d(TAG, "resolveAid: AID " + entry.getKey() + " matches.");
    177                         AidResolveInfo entryResolveInfo = entry.getValue();
    178                         if (entryResolveInfo.defaultService != null) {
    179                             if (resolveInfo.defaultService != null) {
    180                                 // This shouldn't happen; for every prefix we have only one
    181                                 // default service.
    182                                 Log.e(TAG, "Different defaults for conflicting AIDs!");
    183                             }
    184                             resolveInfo.defaultService = entryResolveInfo.defaultService;
    185                             resolveInfo.category = entryResolveInfo.category;
    186                         }
    187                         for (ApduServiceInfo serviceInfo : entryResolveInfo.services) {
    188                             if (!resolveInfo.services.contains(serviceInfo)) {
    189                                 resolveInfo.services.add(serviceInfo);
    190                             }
    191                         }
    192                     }
    193                 }
    194             } else {
    195                 resolveInfo = mAidCache.get(aid);
    196             }
    197             if (DBG) Log.d(TAG, "Resolved to: " + resolveInfo);
    198             return resolveInfo;
    199         }
    200     }
    201 
    202     public boolean supportsAidPrefixRegistration() {
    203         return mSupportsPrefixes;
    204     }
    205 
    206     public boolean supportsAidSubsetRegistration() {
    207         return mSupportsSubset;
    208     }
    209 
    210     public boolean isDefaultServiceForAid(int userId, ComponentName service, String aid) {
    211         AidResolveInfo resolveInfo = resolveAid(aid);
    212         if (resolveInfo == null || resolveInfo.services == null ||
    213                 resolveInfo.services.size() == 0) {
    214             return false;
    215         }
    216 
    217         if (resolveInfo.defaultService != null) {
    218             return service.equals(resolveInfo.defaultService.getComponent());
    219         } else if (resolveInfo.services.size() == 1) {
    220             return service.equals(resolveInfo.services.get(0).getComponent());
    221         } else {
    222             // More than one service, not the default
    223             return false;
    224         }
    225     }
    226 
    227     /**
    228      * Resolves a conflict between multiple services handling the same
    229      * AIDs. Note that the AID itself is not an input to the decision
    230      * process - the algorithm just looks at the competing services
    231      * and what preferences the user has indicated. In short, it works like
    232      * this:
    233      *
    234      * 1) If there is a preferred foreground service, that service wins
    235      * 2) Else, if there is a preferred payment service, that service wins
    236      * 3) Else, if there is no winner, and all conflicting services will be
    237      *    in the list of resolved services.
    238      */
    239      AidResolveInfo resolveAidConflictLocked(Collection<ServiceAidInfo> conflictingServices,
    240                                              boolean makeSingleServiceDefault) {
    241         if (conflictingServices == null || conflictingServices.size() == 0) {
    242             Log.e(TAG, "resolveAidConflict: No services passed in.");
    243             return null;
    244         }
    245         AidResolveInfo resolveInfo = new AidResolveInfo();
    246         resolveInfo.category = CardEmulation.CATEGORY_OTHER;
    247 
    248         ApduServiceInfo matchedForeground = null;
    249         ApduServiceInfo matchedPayment = null;
    250         for (ServiceAidInfo serviceAidInfo : conflictingServices) {
    251             boolean serviceClaimsPaymentAid =
    252                     CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category);
    253             if (serviceAidInfo.service.getComponent().equals(mPreferredForegroundService)) {
    254                 resolveInfo.services.add(serviceAidInfo.service);
    255                 if (serviceClaimsPaymentAid) {
    256                     resolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
    257                 }
    258                 matchedForeground = serviceAidInfo.service;
    259             } else if (serviceAidInfo.service.getComponent().equals(mPreferredPaymentService) &&
    260                     serviceClaimsPaymentAid) {
    261                 resolveInfo.services.add(serviceAidInfo.service);
    262                 resolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
    263                 matchedPayment = serviceAidInfo.service;
    264             } else {
    265                 if (serviceClaimsPaymentAid) {
    266                     // If this service claims it's a payment AID, don't route it,
    267                     // because it's not the default. Otherwise, add it to the list
    268                     // but not as default.
    269                     if (DBG) Log.d(TAG, "resolveAidLocked: (Ignoring handling service " +
    270                             serviceAidInfo.service.getComponent() +
    271                             " because it's not the payment default.)");
    272                 } else {
    273                     resolveInfo.services.add(serviceAidInfo.service);
    274                 }
    275             }
    276         }
    277         if (matchedForeground != null) {
    278             // 1st priority: if the foreground app prefers a service,
    279             // and that service asks for the AID, that service gets it
    280             if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to foreground preferred " +
    281                     matchedForeground);
    282             resolveInfo.defaultService = matchedForeground;
    283         } else if (matchedPayment != null) {
    284             // 2nd priority: if there is a preferred payment service,
    285             // and that service claims this as a payment AID, that service gets it
    286             if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to payment default " +
    287                     "default " + matchedPayment);
    288             resolveInfo.defaultService = matchedPayment;
    289         } else {
    290             if (resolveInfo.services.size() == 1 && makeSingleServiceDefault) {
    291                 if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: making single handling service " +
    292                         resolveInfo.services.get(0).getComponent() + " default.");
    293                 resolveInfo.defaultService = resolveInfo.services.get(0);
    294             } else {
    295                 // Nothing to do, all services already in list
    296                 if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to all matching services");
    297             }
    298         }
    299         return resolveInfo;
    300     }
    301 
    302     class DefaultServiceInfo {
    303         ServiceAidInfo paymentDefault;
    304         ServiceAidInfo foregroundDefault;
    305     }
    306 
    307     DefaultServiceInfo findDefaultServices(ArrayList<ServiceAidInfo> serviceAidInfos) {
    308         DefaultServiceInfo defaultServiceInfo = new DefaultServiceInfo();
    309 
    310         for (ServiceAidInfo serviceAidInfo : serviceAidInfos) {
    311             boolean serviceClaimsPaymentAid =
    312                     CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category);
    313             if (serviceAidInfo.service.getComponent().equals(mPreferredForegroundService)) {
    314                 defaultServiceInfo.foregroundDefault = serviceAidInfo;
    315             } else if (serviceAidInfo.service.getComponent().equals(mPreferredPaymentService) &&
    316                     serviceClaimsPaymentAid) {
    317                 defaultServiceInfo.paymentDefault = serviceAidInfo;
    318             }
    319         }
    320         return defaultServiceInfo;
    321     }
    322 
    323     AidResolveInfo resolveAidConflictLocked(ArrayList<ServiceAidInfo> aidServices,
    324                                                   ArrayList<ServiceAidInfo> conflictingServices) {
    325         // Find defaults among the root AID services themselves
    326         DefaultServiceInfo aidDefaultInfo = findDefaultServices(aidServices);
    327 
    328         // Find any defaults among the children
    329         DefaultServiceInfo conflictingDefaultInfo = findDefaultServices(conflictingServices);
    330         AidResolveInfo resolveinfo;
    331         // Three conditions under which the root AID gets to be the default
    332         // 1. A service registering the root AID is the current foreground preferred
    333         // 2. A service registering the root AID is the current tap & pay default AND
    334         //    no child is the current foreground preferred
    335         // 3. There is only one service for the root AID, and there are no children
    336         if (aidDefaultInfo.foregroundDefault != null) {
    337             if (DBG) Log.d(TAG, "Prefix AID service " +
    338                     aidDefaultInfo.foregroundDefault.service.getComponent() + " has foreground" +
    339                     " preference, ignoring conflicting AIDs.");
    340             // Foreground default trumps any conflicting services, treat as normal AID conflict
    341             // and ignore children
    342             resolveinfo = resolveAidConflictLocked(aidServices, true);
    343             //If the AID is subsetAID check for prefix in same service.
    344             if (isSubset(aidServices.get(0).aid)) {
    345                 resolveinfo.prefixInfo = findPrefixConflictForSubsetAid(aidServices.get(0).aid ,
    346                         new ArrayList<ApduServiceInfo>(){{add(resolveinfo.defaultService);}},true);
    347             }
    348              return resolveinfo;
    349         } else if (aidDefaultInfo.paymentDefault != null) {
    350             // Check if any of the conflicting services is foreground default
    351             if (conflictingDefaultInfo.foregroundDefault != null) {
    352                 // Conflicting AID registration is in foreground, trumps prefix tap&pay default
    353                 if (DBG) Log.d(TAG, "One of the conflicting AID registrations is foreground " +
    354                         "preferred, ignoring prefix.");
    355                 return EMPTY_RESOLVE_INFO;
    356             } else {
    357                 // Prefix service is tap&pay default, treat as normal AID conflict for just prefix
    358                 if (DBG) Log.d(TAG, "Prefix AID service " +
    359                     aidDefaultInfo.paymentDefault.service.getComponent() + " is payment" +
    360                         " default, ignoring conflicting AIDs.");
    361                 resolveinfo = resolveAidConflictLocked(aidServices, true);
    362                 //If the AID is subsetAID check for prefix in same service.
    363                 if (isSubset(aidServices.get(0).aid)) {
    364                     resolveinfo.prefixInfo = findPrefixConflictForSubsetAid(aidServices.get(0).aid ,
    365                         new ArrayList<ApduServiceInfo>(){{add(resolveinfo.defaultService);}},true);
    366                 }
    367                 return resolveinfo;
    368             }
    369         } else {
    370             if (conflictingDefaultInfo.foregroundDefault != null ||
    371                     conflictingDefaultInfo.paymentDefault != null) {
    372                 if (DBG) Log.d(TAG, "One of the conflicting AID registrations is either payment " +
    373                         "default or foreground preferred, ignoring prefix.");
    374                 return EMPTY_RESOLVE_INFO;
    375             } else {
    376                 // No children that are preferred; add all services of the root
    377                 // make single service default if no children are present
    378                 if (DBG) Log.d(TAG, "No service has preference, adding all.");
    379                 resolveinfo = resolveAidConflictLocked(aidServices, conflictingServices.isEmpty());
    380                 //If the AID is subsetAID check for conflicting prefix in all
    381                 //conflciting services and root services.
    382                 if (isSubset(aidServices.get(0).aid)) {
    383                     ArrayList <ApduServiceInfo> apduServiceList = new  ArrayList <ApduServiceInfo>();
    384                     for (ServiceAidInfo serviceInfo : conflictingServices)
    385                         apduServiceList.add(serviceInfo.service);
    386                     for (ServiceAidInfo serviceInfo : aidServices)
    387                         apduServiceList.add(serviceInfo.service);
    388                     resolveinfo.prefixInfo =
    389                          findPrefixConflictForSubsetAid(aidServices.get(0).aid ,apduServiceList,false);
    390                 }
    391                 return resolveinfo;
    392             }
    393         }
    394     }
    395 
    396     void generateServiceMapLocked(List<ApduServiceInfo> services) {
    397         // Easiest is to just build the entire tree again
    398         mAidServices.clear();
    399         for (ApduServiceInfo service : services) {
    400             if (DBG) Log.d(TAG, "generateServiceMap component: " + service.getComponent());
    401             List<String> prefixAids = service.getPrefixAids();
    402             List<String> subSetAids = service.getSubsetAids();
    403 
    404             for (String aid : service.getAids()) {
    405                 if (!CardEmulation.isValidAid(aid)) {
    406                     Log.e(TAG, "Aid " + aid + " is not valid.");
    407                     continue;
    408                 }
    409                 if (aid.endsWith("*") && !supportsAidPrefixRegistration()) {
    410                     Log.e(TAG, "Prefix AID " + aid + " ignored on device that doesn't support it.");
    411                     continue;
    412                 } else if (supportsAidPrefixRegistration() && prefixAids.size() > 0 && isExact(aid)) {
    413                     // Check if we already have an overlapping prefix registered for this AID
    414                     boolean foundPrefix = false;
    415                     for (String prefixAid : prefixAids) {
    416                         String prefix = prefixAid.substring(0, prefixAid.length() - 1);
    417                         if (aid.startsWith(prefix)) {
    418                             Log.e(TAG, "Ignoring exact AID " + aid + " because prefix AID " + prefixAid +
    419                                     " is already registered");
    420                             foundPrefix = true;
    421                             break;
    422                         }
    423                     }
    424                     if (foundPrefix) {
    425                         continue;
    426                     }
    427                 } else if (aid.endsWith("#") && !supportsAidSubsetRegistration()) {
    428                     Log.e(TAG, "Subset AID " + aid + " ignored on device that doesn't support it.");
    429                     continue;
    430                 } else if (supportsAidSubsetRegistration() && subSetAids.size() > 0 && isExact(aid)) {
    431                     // Check if we already have an overlapping subset registered for this AID
    432                     boolean foundSubset = false;
    433                     for (String subsetAid : subSetAids) {
    434                         String plainSubset = subsetAid.substring(0, subsetAid.length() - 1);
    435                         if (plainSubset.startsWith(aid)) {
    436                             Log.e(TAG, "Ignoring exact AID " + aid + " because subset AID " + plainSubset +
    437                                     " is already registered");
    438                             foundSubset = true;
    439                             break;
    440                         }
    441                     }
    442                     if (foundSubset) {
    443                         continue;
    444                     }
    445                 }
    446 
    447                 ServiceAidInfo serviceAidInfo = new ServiceAidInfo();
    448                 serviceAidInfo.aid = aid.toUpperCase();
    449                 serviceAidInfo.service = service;
    450                 serviceAidInfo.category = service.getCategoryForAid(aid);
    451 
    452                 if (mAidServices.containsKey(serviceAidInfo.aid)) {
    453                     final ArrayList<ServiceAidInfo> serviceAidInfos =
    454                             mAidServices.get(serviceAidInfo.aid);
    455                     serviceAidInfos.add(serviceAidInfo);
    456                 } else {
    457                     final ArrayList<ServiceAidInfo> serviceAidInfos =
    458                             new ArrayList<ServiceAidInfo>();
    459                     serviceAidInfos.add(serviceAidInfo);
    460                     mAidServices.put(serviceAidInfo.aid, serviceAidInfos);
    461                 }
    462             }
    463         }
    464     }
    465 
    466     static boolean isExact(String aid) {
    467         return (!((aid.endsWith("*") || (aid.endsWith("#")))));
    468     }
    469 
    470     static boolean isPrefix(String aid) {
    471         return aid.endsWith("*");
    472     }
    473 
    474     static boolean isSubset(String aid) {
    475         return aid.endsWith("#");
    476     }
    477 
    478     final class ReslovedPrefixConflictAid {
    479         String prefixAid = null;
    480         boolean matchingSubset = false;
    481     }
    482 
    483     final class AidConflicts {
    484         NavigableMap<String, ArrayList<ServiceAidInfo>> conflictMap;
    485         final ArrayList<ServiceAidInfo> services = new ArrayList<ServiceAidInfo>();
    486         final HashSet<String> aids = new HashSet<String>();
    487     }
    488 
    489     ReslovedPrefixConflictAid findPrefixConflictForSubsetAid(String subsetAid ,
    490             ArrayList<ApduServiceInfo> prefixServices, boolean priorityRootAid){
    491         ArrayList<String> prefixAids = new ArrayList<String>();
    492         String minPrefix = null;
    493         //This functions checks whether there is a prefix AID matching to subset AID
    494         //Because both the subset AID and matching smaller perfix are to be added to routing table.
    495         //1.Finds the prefix matching AID in the services sent.
    496         //2.Find the smallest prefix among matching prefix and add it only if it is not same as susbet AID.
    497         //3..If the subset AID and prefix AID are same add only one AID with both prefix , subset bits set.
    498         // Cut off "#"
    499         String plainSubsetAid = subsetAid.substring(0, subsetAid.length() - 1);
    500         for (ApduServiceInfo service : prefixServices) {
    501             for (String prefixAid : service.getPrefixAids()) {
    502                 // Cut off "#"
    503                 String plainPrefix= prefixAid.substring(0, prefixAid.length() - 1);
    504                 if( plainSubsetAid.startsWith(plainPrefix)) {
    505                     if (priorityRootAid) {
    506                        if (CardEmulation.CATEGORY_PAYMENT.equals(service.getCategoryForAid(prefixAid)) ||
    507                                (service.getComponent().equals(mPreferredForegroundService)))
    508                            prefixAids.add(prefixAid);
    509                     } else {
    510                         prefixAids.add(prefixAid);
    511                     }
    512                 }
    513             }
    514         }
    515         if (prefixAids.size() > 0)
    516             minPrefix = Collections.min(prefixAids);
    517         ReslovedPrefixConflictAid resolvedPrefix = new ReslovedPrefixConflictAid();
    518         resolvedPrefix.prefixAid = minPrefix;
    519         if ((minPrefix != null ) &&
    520                 plainSubsetAid.equalsIgnoreCase(minPrefix.substring(0, minPrefix.length() - 1)))
    521             resolvedPrefix.matchingSubset = true;
    522         return resolvedPrefix;
    523     }
    524 
    525     AidConflicts findConflictsForPrefixLocked(String prefixAid) {
    526         AidConflicts prefixConflicts = new AidConflicts();
    527         String plainAid = prefixAid.substring(0, prefixAid.length() - 1); // Cut off "*"
    528         String lastAidWithPrefix = String.format("%-32s", plainAid).replace(' ', 'F');
    529         if (DBG) Log.d(TAG, "Finding AIDs in range [" + plainAid + " - " +
    530                 lastAidWithPrefix + "]");
    531         prefixConflicts.conflictMap =
    532                 mAidServices.subMap(plainAid, true, lastAidWithPrefix, true);
    533         for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry :
    534                 prefixConflicts.conflictMap.entrySet()) {
    535             if (!entry.getKey().equalsIgnoreCase(prefixAid)) {
    536                 if (DBG)
    537                     Log.d(TAG, "AID " + entry.getKey() + " conflicts with prefix; " +
    538                             " adding handling services for conflict resolution.");
    539                 prefixConflicts.services.addAll(entry.getValue());
    540                 prefixConflicts.aids.add(entry.getKey());
    541             }
    542         }
    543         return prefixConflicts;
    544     }
    545 
    546     AidConflicts findConflictsForSubsetAidLocked(String subsetAid) {
    547         AidConflicts subsetConflicts = new AidConflicts();
    548         // Cut off "@"
    549         String lastPlainAid = subsetAid.substring(0, subsetAid.length() - 1);
    550         // Cut off "@"
    551         String plainSubsetAid = subsetAid.substring(0, subsetAid.length() - 1);
    552         String firstAid = subsetAid.substring(0, 10);
    553         if (DBG) Log.d(TAG, "Finding AIDs in range [" + firstAid + " - " +
    554             lastPlainAid + "]");
    555         subsetConflicts.conflictMap = new TreeMap();
    556         for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry :
    557             mAidServices.entrySet()) {
    558             String aid = entry.getKey();
    559             String plainAid = aid;
    560             if (isSubset(aid) || isPrefix(aid))
    561                 plainAid = aid.substring(0, aid.length() - 1);
    562             if (plainSubsetAid.startsWith(plainAid))
    563                 subsetConflicts.conflictMap.put(entry.getKey(),entry.getValue());
    564         }
    565         for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry :
    566             subsetConflicts.conflictMap.entrySet()) {
    567             if (!entry.getKey().equalsIgnoreCase(subsetAid)) {
    568                 if (DBG)
    569                     Log.d(TAG, "AID " + entry.getKey() + " conflicts with subset AID; " +
    570                             " adding handling services for conflict resolution.");
    571                 subsetConflicts.services.addAll(entry.getValue());
    572                 subsetConflicts.aids.add(entry.getKey());
    573             }
    574         }
    575         return subsetConflicts;
    576     }
    577 
    578     void generateAidCacheLocked() {
    579         mAidCache.clear();
    580         // Get all exact and prefix AIDs in an ordered list
    581         final TreeMap<String, AidResolveInfo> aidCache = new TreeMap<String, AidResolveInfo>();
    582 
    583         //aidCache is temproary cache for geenrating the first prefix based lookup table.
    584         PriorityQueue<String> aidsToResolve = new PriorityQueue<String>(mAidServices.keySet());
    585         aidCache.clear();
    586         while (!aidsToResolve.isEmpty()) {
    587             final ArrayList<String> resolvedAids = new ArrayList<String>();
    588 
    589             String aidToResolve = aidsToResolve.peek();
    590             // Because of the lexicographical ordering, all following AIDs either start with the
    591             // same bytes and are longer, or start with different bytes.
    592 
    593             // A special case is if another service registered the same AID as a prefix, in
    594             // which case we want to start with that AID, since it conflicts with this one
    595             // All exact and suffix and prefix AID must be checked for conflicting cases
    596             if (aidsToResolve.contains(aidToResolve + "*")) {
    597                 aidToResolve = aidToResolve + "*";
    598             }
    599             if (DBG) Log.d(TAG, "generateAidCacheLocked: starting with aid " + aidToResolve);
    600 
    601             if (isPrefix(aidToResolve)) {
    602                 // This AID itself is a prefix; let's consider this prefix as the "root",
    603                 // and all conflicting AIDs as its children.
    604                 // For example, if "A000000003*" is the prefix root,
    605                 // "A000000003", "A00000000301*", "A0000000030102" are all conflicting children AIDs
    606                 final ArrayList<ServiceAidInfo> prefixServices = new ArrayList<ServiceAidInfo>(
    607                         mAidServices.get(aidToResolve));
    608 
    609                 // Find all conflicting children services
    610                 AidConflicts prefixConflicts = findConflictsForPrefixLocked(aidToResolve);
    611 
    612                 // Resolve conflicts
    613                 AidResolveInfo resolveInfo = resolveAidConflictLocked(prefixServices,
    614                         prefixConflicts.services);
    615                 aidCache.put(aidToResolve, resolveInfo);
    616                 resolvedAids.add(aidToResolve);
    617                 if (resolveInfo.defaultService != null) {
    618                     // This prefix is the default; therefore, AIDs of all conflicting children
    619                     // will no longer be evaluated.
    620                     resolvedAids.addAll(prefixConflicts.aids);
    621                     for (String aid : resolveInfo.defaultService.getSubsetAids()) {
    622                         if (prefixConflicts.aids.contains(aid)) {
    623                             if ((CardEmulation.CATEGORY_PAYMENT.equals(resolveInfo.defaultService.getCategoryForAid(aid))) ||
    624                                     (resolveInfo.defaultService.getComponent().equals(mPreferredForegroundService))) {
    625                                 AidResolveInfo childResolveInfo = resolveAidConflictLocked(mAidServices.get(aid), false);
    626                                 aidCache.put(aid,childResolveInfo);
    627                                 Log.d(TAG, "AID " + aid+ " shared with prefix; " +
    628                                                 "adding subset .");
    629                              }
    630                         }
    631                    }
    632                 } else if (resolveInfo.services.size() > 0) {
    633                     // This means we don't have a default for this prefix and all its
    634                     // conflicting children. So, for all conflicting AIDs, just add
    635                     // all handling services without setting a default
    636                     boolean foundChildService = false;
    637                     for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry :
    638                             prefixConflicts.conflictMap.entrySet()) {
    639                         if (!entry.getKey().equalsIgnoreCase(aidToResolve)) {
    640                             if (DBG)
    641                                 Log.d(TAG, "AID " + entry.getKey() + " shared with prefix; " +
    642                                         " adding all handling services.");
    643                             AidResolveInfo childResolveInfo = resolveAidConflictLocked(
    644                                     entry.getValue(), false);
    645                             // Special case: in this case all children AIDs must be routed to the
    646                             // host, so we can ask the user which service is preferred.
    647                             // Since these are all "children" of the prefix, they don't need
    648                             // to be routed, since the prefix will already get routed to the host
    649                             childResolveInfo.mustRoute = false;
    650                             aidCache.put(entry.getKey(),childResolveInfo);
    651                             resolvedAids.add(entry.getKey());
    652                             foundChildService |= !childResolveInfo.services.isEmpty();
    653                         }
    654                     }
    655                     // Special case: if in the end we didn't add any children services,
    656                     // and the prefix has only one service, make that default
    657                     if (!foundChildService && resolveInfo.services.size() == 1) {
    658                         resolveInfo.defaultService = resolveInfo.services.get(0);
    659                     }
    660                 } else {
    661                     // This prefix is not handled at all; we will evaluate
    662                     // the children separately in next passes.
    663                 }
    664             } else {
    665                 // Exact AID and no other conflicting AID registrations present
    666                 // This is true because aidsToResolve is lexicographically ordered, and
    667                 // so by necessity all other AIDs are different than this AID or longer.
    668                 if (DBG) Log.d(TAG, "Exact AID, resolving.");
    669                 final ArrayList<ServiceAidInfo> conflictingServiceInfos =
    670                         new ArrayList<ServiceAidInfo>(mAidServices.get(aidToResolve));
    671                 aidCache.put(aidToResolve, resolveAidConflictLocked(conflictingServiceInfos, true));
    672                 resolvedAids.add(aidToResolve);
    673             }
    674 
    675             // Remove the AIDs we resolved from the list of AIDs to resolve
    676             if (DBG) Log.d(TAG, "AIDs: " + resolvedAids + " were resolved.");
    677             aidsToResolve.removeAll(resolvedAids);
    678             resolvedAids.clear();
    679         }
    680         PriorityQueue<String> reversedQueue = new PriorityQueue<String>(1, Collections.reverseOrder());
    681         reversedQueue.addAll(aidCache.keySet());
    682         while (!reversedQueue.isEmpty()) {
    683             final ArrayList<String> resolvedAids = new ArrayList<String>();
    684 
    685             String aidToResolve = reversedQueue.peek();
    686             if (isPrefix(aidToResolve)) {
    687                 String matchingSubset = aidToResolve.substring(0,aidToResolve.length()-1 ) + "#";
    688                 if (DBG) Log.d(TAG, "matching subset"+matchingSubset);
    689                 if (reversedQueue.contains(matchingSubset))
    690                      aidToResolve = aidToResolve.substring(0,aidToResolve.length()-1) + "#";
    691             }
    692             if (isSubset(aidToResolve)) {
    693                 if (DBG) Log.d(TAG, "subset resolving aidToResolve  "+aidToResolve);
    694                 final ArrayList<ServiceAidInfo> subsetServices = new ArrayList<ServiceAidInfo>(
    695                         mAidServices.get(aidToResolve));
    696 
    697                 // Find all conflicting children services
    698                 AidConflicts aidConflicts = findConflictsForSubsetAidLocked(aidToResolve);
    699 
    700                 // Resolve conflicts
    701                 AidResolveInfo resolveInfo = resolveAidConflictLocked(subsetServices,
    702                         aidConflicts.services);
    703                 mAidCache.put(aidToResolve, resolveInfo);
    704                 resolvedAids.add(aidToResolve);
    705                 if (resolveInfo.defaultService != null) {
    706                     // This subset is the default; therefore, AIDs of all conflicting children
    707                     // will no longer be evaluated.Check for any prefix matching in the same service
    708                     if (resolveInfo.prefixInfo != null && resolveInfo.prefixInfo.prefixAid != null &&
    709                             !resolveInfo.prefixInfo.matchingSubset) {
    710                         if (DBG)
    711                             Log.d(TAG, "AID default " + resolveInfo.prefixInfo.prefixAid +
    712                                     " prefix AID shared with dsubset root; " +
    713                                     " adding prefix aid");
    714                         AidResolveInfo childResolveInfo = resolveAidConflictLocked(
    715                         mAidServices.get(resolveInfo.prefixInfo.prefixAid), false);
    716                         mAidCache.put(resolveInfo.prefixInfo.prefixAid, childResolveInfo);
    717                     }
    718                     resolvedAids.addAll(aidConflicts.aids);
    719                 } else if (resolveInfo.services.size() > 0) {
    720                     // This means we don't have a default for this subset and all its
    721                     // conflicting children. So, for all conflicting AIDs, just add
    722                     // all handling services without setting a default
    723                     boolean foundChildService = false;
    724                     for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry :
    725                         aidConflicts.conflictMap.entrySet()) {
    726                         // We need to add shortest prefix among them.
    727                         if (!entry.getKey().equalsIgnoreCase(aidToResolve)) {
    728                             if (DBG)
    729                                 Log.d(TAG, "AID " + entry.getKey() + " shared with subset root; " +
    730                                         " adding all handling services.");
    731                             AidResolveInfo childResolveInfo = resolveAidConflictLocked(
    732                                 entry.getValue(), false);
    733                             // Special case: in this case all children AIDs must be routed to the
    734                             // host, so we can ask the user which service is preferred.
    735                             // Since these are all "children" of the subset, they don't need
    736                             // to be routed, since the subset will already get routed to the host
    737                             childResolveInfo.mustRoute = false;
    738                             mAidCache.put(entry.getKey(),childResolveInfo);
    739                             resolvedAids.add(entry.getKey());
    740                             foundChildService |= !childResolveInfo.services.isEmpty();
    741                         }
    742                     }
    743                     if(resolveInfo.prefixInfo != null &&
    744                             resolveInfo.prefixInfo.prefixAid != null &&
    745                             !resolveInfo.prefixInfo.matchingSubset) {
    746                         AidResolveInfo childResolveInfo = resolveAidConflictLocked(
    747                         mAidServices.get(resolveInfo.prefixInfo.prefixAid), false);
    748                         mAidCache.put(resolveInfo.prefixInfo.prefixAid, childResolveInfo);
    749                         if (DBG)
    750                             Log.d(TAG, "AID " + resolveInfo.prefixInfo.prefixAid +
    751                                     " prefix AID shared with subset root; " +
    752                                     " adding prefix aid");
    753                     }
    754                     // Special case: if in the end we didn't add any children services,
    755                     // and the subset has only one service, make that default
    756                     if (!foundChildService && resolveInfo.services.size() == 1) {
    757                         resolveInfo.defaultService = resolveInfo.services.get(0);
    758                     }
    759                 } else {
    760                     // This subset is not handled at all; we will evaluate
    761                     // the children separately in next passes.
    762                 }
    763             } else {
    764                 // Exact AID and no other conflicting AID registrations present. This is
    765                 // true because reversedQueue is lexicographically ordered in revrese, and
    766                 // so by necessity all other AIDs are different than this AID or shorter.
    767                 if (DBG) Log.d(TAG, "Exact or Prefix AID."+aidToResolve);
    768                 mAidCache.put(aidToResolve, aidCache.get(aidToResolve));
    769                 resolvedAids.add(aidToResolve);
    770             }
    771 
    772             // Remove the AIDs we resolved from the list of AIDs to resolve
    773             if (DBG) Log.d(TAG, "AIDs: " + resolvedAids + " were resolved.");
    774             reversedQueue.removeAll(resolvedAids);
    775             resolvedAids.clear();
    776         }
    777 
    778         updateRoutingLocked();
    779     }
    780 
    781     void updateRoutingLocked() {
    782         if (!mNfcEnabled) {
    783             if (DBG) Log.d(TAG, "Not updating routing table because NFC is off.");
    784             return;
    785         }
    786         final HashMap<String, AidRoutingManager.AidEntry> routingEntries = Maps.newHashMap();
    787         // For each AID, find interested services
    788         for (Map.Entry<String, AidResolveInfo> aidEntry:
    789                 mAidCache.entrySet()) {
    790             String aid = aidEntry.getKey();
    791             AidResolveInfo resolveInfo = aidEntry.getValue();
    792             if (!resolveInfo.mustRoute) {
    793                 if (DBG) Log.d(TAG, "Not routing AID " + aid + " on request.");
    794                 continue;
    795             }
    796             AidRoutingManager.AidEntry aidType = mRoutingManager.new AidEntry();
    797             if (aid.endsWith("#")) {
    798                 aidType.aidInfo |= AID_ROUTE_QUAL_SUBSET;
    799             }
    800             if(aid.endsWith("*") || (resolveInfo.prefixInfo != null &&
    801                     resolveInfo.prefixInfo.matchingSubset)) {
    802                 aidType.aidInfo |= AID_ROUTE_QUAL_PREFIX;
    803             }
    804             if (resolveInfo.services.size() == 0) {
    805                 // No interested services
    806             } else if (resolveInfo.defaultService != null) {
    807                 // There is a default service set, route to where that service resides -
    808                 // either on the host (HCE) or on an SE.
    809                 aidType.isOnHost = resolveInfo.defaultService.isOnHost();
    810                 routingEntries.put(aid, aidType);
    811             } else if (resolveInfo.services.size() == 1) {
    812                 // Only one service, but not the default, must route to host
    813                 // to ask the user to choose one.
    814                 aidType.isOnHost = true;
    815                 routingEntries.put(aid, aidType);
    816             } else if (resolveInfo.services.size() > 1) {
    817                 // Multiple services, need to route to host to ask
    818                 aidType.isOnHost = true;
    819                 routingEntries.put(aid, aidType);
    820             }
    821         }
    822         mRoutingManager.configureRouting(routingEntries);
    823     }
    824 
    825     public void onServicesUpdated(int userId, List<ApduServiceInfo> services) {
    826         if (DBG) Log.d(TAG, "onServicesUpdated");
    827         synchronized (mLock) {
    828             if (ActivityManager.getCurrentUser() == userId) {
    829                 // Rebuild our internal data-structures
    830                 generateServiceMapLocked(services);
    831                 generateAidCacheLocked();
    832             } else {
    833                 if (DBG) Log.d(TAG, "Ignoring update because it's not for the current user.");
    834             }
    835         }
    836     }
    837 
    838     public void onPreferredPaymentServiceChanged(ComponentName service) {
    839         if (DBG) Log.d(TAG, "Preferred payment service changed.");
    840        synchronized (mLock) {
    841            mPreferredPaymentService = service;
    842            generateAidCacheLocked();
    843        }
    844     }
    845 
    846     public void onPreferredForegroundServiceChanged(ComponentName service) {
    847         if (DBG) Log.d(TAG, "Preferred foreground service changed.");
    848         synchronized (mLock) {
    849             mPreferredForegroundService = service;
    850             generateAidCacheLocked();
    851         }
    852     }
    853 
    854     public void onNfcDisabled() {
    855         synchronized (mLock) {
    856             mNfcEnabled = false;
    857         }
    858         mRoutingManager.onNfccRoutingTableCleared();
    859     }
    860 
    861     public void onNfcEnabled() {
    862         synchronized (mLock) {
    863             mNfcEnabled = true;
    864             updateRoutingLocked();
    865         }
    866     }
    867 
    868     String dumpEntry(Map.Entry<String, AidResolveInfo> entry) {
    869         StringBuilder sb = new StringBuilder();
    870         String category = entry.getValue().category;
    871         ApduServiceInfo defaultServiceInfo = entry.getValue().defaultService;
    872         sb.append("    \"" + entry.getKey() + "\" (category: " + category + ")\n");
    873         ComponentName defaultComponent = defaultServiceInfo != null ?
    874                 defaultServiceInfo.getComponent() : null;
    875 
    876         for (ApduServiceInfo serviceInfo : entry.getValue().services) {
    877             sb.append("        ");
    878             if (serviceInfo.getComponent().equals(defaultComponent)) {
    879                 sb.append("*DEFAULT* ");
    880             }
    881             sb.append(serviceInfo.getComponent() +
    882                     " (Description: " + serviceInfo.getDescription() + ")\n");
    883         }
    884         return sb.toString();
    885     }
    886 
    887     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    888         pw.println("    AID cache entries: ");
    889         for (Map.Entry<String, AidResolveInfo> entry : mAidCache.entrySet()) {
    890             pw.println(dumpEntry(entry));
    891         }
    892         pw.println("    Service preferred by foreground app: " + mPreferredForegroundService);
    893         pw.println("    Preferred payment service: " + mPreferredPaymentService);
    894         pw.println("");
    895         mRoutingManager.dump(fd, pw, args);
    896         pw.println("");
    897     }
    898 }
    899