Home | History | Annotate | Download | only in cardemulation
      1 package com.android.nfc.cardemulation;
      2 
      3 import android.app.ActivityManager;
      4 import android.content.ComponentName;
      5 import android.content.Context;
      6 import android.content.Intent;
      7 import android.database.ContentObserver;
      8 import android.net.Uri;
      9 import android.nfc.cardemulation.ApduServiceInfo;
     10 import android.nfc.cardemulation.CardEmulation;
     11 import android.nfc.cardemulation.ApduServiceInfo.AidGroup;
     12 import android.os.Handler;
     13 import android.os.Looper;
     14 import android.os.UserHandle;
     15 import android.provider.Settings;
     16 import android.util.Log;
     17 
     18 import com.google.android.collect.Maps;
     19 
     20 import java.io.FileDescriptor;
     21 import java.io.PrintWriter;
     22 import java.util.ArrayList;
     23 import java.util.HashMap;
     24 import java.util.HashSet;
     25 import java.util.List;
     26 import java.util.Map;
     27 import java.util.Set;
     28 import java.util.SortedMap;
     29 import java.util.TreeMap;
     30 
     31 public class RegisteredAidCache implements RegisteredServicesCache.Callback {
     32     static final String TAG = "RegisteredAidCache";
     33 
     34     static final boolean DBG = false;
     35 
     36     // mAidServices is a tree that maps an AID to a list of handling services
     37     // on Android. It is only valid for the current user.
     38     final TreeMap<String, ArrayList<ApduServiceInfo>> mAidToServices =
     39             new TreeMap<String, ArrayList<ApduServiceInfo>>();
     40 
     41     // mAidCache is a lookup table for quickly mapping an AID to one or
     42     // more services. It differs from mAidServices in the sense that it
     43     // has already accounted for defaults, and hence its return value
     44     // is authoritative for the current set of services and defaults.
     45     // It is only valid for the current user.
     46     final HashMap<String, AidResolveInfo> mAidCache =
     47             Maps.newHashMap();
     48 
     49     final HashMap<String, ComponentName> mCategoryDefaults =
     50             Maps.newHashMap();
     51 
     52     final class AidResolveInfo {
     53         List<ApduServiceInfo> services;
     54         ApduServiceInfo defaultService;
     55         String aid;
     56     }
     57 
     58     /**
     59      * AIDs per category
     60      */
     61     public final HashMap<String, Set<String>> mCategoryAids =
     62             Maps.newHashMap();
     63 
     64     final Handler mHandler = new Handler(Looper.getMainLooper());
     65     final RegisteredServicesCache mServiceCache;
     66 
     67     final Object mLock = new Object();
     68     final Context mContext;
     69     final AidRoutingManager mRoutingManager;
     70     final SettingsObserver mSettingsObserver;
     71 
     72     ComponentName mNextTapComponent = null;
     73     boolean mNfcEnabled = false;
     74 
     75     private final class SettingsObserver extends ContentObserver {
     76         public SettingsObserver(Handler handler) {
     77             super(handler);
     78         }
     79 
     80         @Override
     81         public void onChange(boolean selfChange, Uri uri) {
     82             super.onChange(selfChange, uri);
     83             synchronized (mLock) {
     84                 // Do it just for the current user. If it was in fact
     85                 // a change made for another user, we'll sync it down
     86                 // on user switch.
     87                 int currentUser = ActivityManager.getCurrentUser();
     88                 boolean changed = updateFromSettingsLocked(currentUser);
     89                 if (changed) {
     90                     generateAidCacheLocked();
     91                     updateRoutingLocked();
     92                 } else {
     93                     if (DBG) Log.d(TAG, "Not updating aid cache + routing: nothing changed.");
     94                 }
     95             }
     96         }
     97     };
     98 
     99     public RegisteredAidCache(Context context, AidRoutingManager routingManager) {
    100         mSettingsObserver = new SettingsObserver(mHandler);
    101         mContext = context;
    102         mServiceCache = new RegisteredServicesCache(context, this);
    103         mRoutingManager = routingManager;
    104 
    105         mContext.getContentResolver().registerContentObserver(
    106                 Settings.Secure.getUriFor(Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT),
    107                 true, mSettingsObserver, UserHandle.USER_ALL);
    108         updateFromSettingsLocked(ActivityManager.getCurrentUser());
    109     }
    110 
    111     public boolean isNextTapOverriden() {
    112         synchronized (mLock) {
    113             return mNextTapComponent != null;
    114         }
    115     }
    116 
    117     public AidResolveInfo resolveAidPrefix(String aid) {
    118         synchronized (mLock) {
    119             char nextAidChar = (char) (aid.charAt(aid.length() - 1) + 1);
    120             String nextAid = aid.substring(0, aid.length() - 1) + nextAidChar;
    121             SortedMap<String, ArrayList<ApduServiceInfo>> matches =
    122                     mAidToServices.subMap(aid, nextAid);
    123             // The first match is lexicographically closest to what the reader asked;
    124             if (matches.isEmpty()) {
    125                 return null;
    126             } else {
    127                 AidResolveInfo resolveInfo = mAidCache.get(matches.firstKey());
    128                 // Let the caller know which AID got selected
    129                 resolveInfo.aid = matches.firstKey();
    130                 return resolveInfo;
    131             }
    132         }
    133     }
    134 
    135     public String getCategoryForAid(String aid) {
    136         synchronized (mLock) {
    137             Set<String> paymentAids = mCategoryAids.get(CardEmulation.CATEGORY_PAYMENT);
    138             if (paymentAids != null && paymentAids.contains(aid)) {
    139                 return CardEmulation.CATEGORY_PAYMENT;
    140             } else {
    141                 return CardEmulation.CATEGORY_OTHER;
    142             }
    143         }
    144     }
    145 
    146     public boolean isDefaultServiceForAid(int userId, ComponentName service, String aid) {
    147         AidResolveInfo resolveInfo = null;
    148         boolean serviceFound = false;
    149         synchronized (mLock) {
    150             serviceFound = mServiceCache.hasService(userId, service);
    151         }
    152         if (!serviceFound) {
    153             // If we don't know about this service yet, it may have just been enabled
    154             // using PackageManager.setComponentEnabledSetting(). The PackageManager
    155             // broadcasts are delayed by 10 seconds in that scenario, which causes
    156             // calls to our APIs referencing that service to fail.
    157             // Hence, update the cache in case we don't know about the service.
    158             if (DBG) Log.d(TAG, "Didn't find passed in service, invalidating cache.");
    159             mServiceCache.invalidateCache(userId);
    160         }
    161         synchronized (mLock) {
    162             resolveInfo = mAidCache.get(aid);
    163         }
    164         if (resolveInfo == null || resolveInfo.services == null ||
    165                 resolveInfo.services.size() == 0) {
    166             return false;
    167         }
    168 
    169         if (resolveInfo.defaultService != null) {
    170             return service.equals(resolveInfo.defaultService.getComponent());
    171         } else if (resolveInfo.services.size() == 1) {
    172             return service.equals(resolveInfo.services.get(0).getComponent());
    173         } else {
    174             // More than one service, not the default
    175             return false;
    176         }
    177     }
    178 
    179     public boolean setDefaultServiceForCategory(int userId, ComponentName service,
    180             String category) {
    181         if (!CardEmulation.CATEGORY_PAYMENT.equals(category)) {
    182             Log.e(TAG, "Not allowing defaults for category " + category);
    183             return false;
    184         }
    185         synchronized (mLock) {
    186             // TODO Not really nice to be writing to Settings.Secure here...
    187             // ideally we overlay our local changes over whatever is in
    188             // Settings.Secure
    189             if (service == null || mServiceCache.hasService(userId, service)) {
    190                 Settings.Secure.putStringForUser(mContext.getContentResolver(),
    191                         Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
    192                         service != null ? service.flattenToString() : null, userId);
    193             } else {
    194                 Log.e(TAG, "Could not find default service to make default: " + service);
    195             }
    196         }
    197         return true;
    198     }
    199 
    200     public boolean isDefaultServiceForCategory(int userId, String category,
    201             ComponentName service) {
    202         boolean serviceFound = false;
    203         synchronized (mLock) {
    204             // If we don't know about this service yet, it may have just been enabled
    205             // using PackageManager.setComponentEnabledSetting(). The PackageManager
    206             // broadcasts are delayed by 10 seconds in that scenario, which causes
    207             // calls to our APIs referencing that service to fail.
    208             // Hence, update the cache in case we don't know about the service.
    209             serviceFound = mServiceCache.hasService(userId, service);
    210         }
    211         if (!serviceFound) {
    212             if (DBG) Log.d(TAG, "Didn't find passed in service, invalidating cache.");
    213             mServiceCache.invalidateCache(userId);
    214         }
    215         ComponentName defaultService =
    216                 getDefaultServiceForCategory(userId, category, true);
    217         return (defaultService != null && defaultService.equals(service));
    218     }
    219 
    220     ComponentName getDefaultServiceForCategory(int userId, String category,
    221             boolean validateInstalled) {
    222         if (!CardEmulation.CATEGORY_PAYMENT.equals(category)) {
    223             Log.e(TAG, "Not allowing defaults for category " + category);
    224             return null;
    225         }
    226         synchronized (mLock) {
    227             // Load current payment default from settings
    228             String name = Settings.Secure.getStringForUser(
    229                     mContext.getContentResolver(), Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
    230                     userId);
    231             if (name != null) {
    232                 ComponentName service = ComponentName.unflattenFromString(name);
    233                 if (!validateInstalled || service == null) {
    234                     return service;
    235                 } else {
    236                     return mServiceCache.hasService(userId, service) ? service : null;
    237                 }
    238             } else {
    239                 return null;
    240             }
    241         }
    242     }
    243 
    244     public List<ApduServiceInfo> getServicesForCategory(int userId, String category) {
    245         return mServiceCache.getServicesForCategory(userId, category);
    246     }
    247 
    248     public boolean setDefaultForNextTap(int userId, ComponentName service) {
    249         synchronized (mLock) {
    250             if (service != null) {
    251                 mNextTapComponent = service;
    252             } else {
    253                 mNextTapComponent = null;
    254             }
    255             // Update cache and routing table
    256             generateAidCacheLocked();
    257             updateRoutingLocked();
    258         }
    259         return true;
    260     }
    261 
    262     /**
    263      * Resolves an AID to a set of services that can handle it.
    264      */
    265      AidResolveInfo resolveAidLocked(List<ApduServiceInfo> resolvedServices, String aid) {
    266         if (resolvedServices == null || resolvedServices.size() == 0) {
    267             if (DBG) Log.d(TAG, "Could not resolve AID " + aid + " to any service.");
    268             return null;
    269         }
    270         AidResolveInfo resolveInfo = new AidResolveInfo();
    271         if (DBG) Log.d(TAG, "resolveAidLocked: resolving AID " + aid);
    272         resolveInfo.services = new ArrayList<ApduServiceInfo>();
    273         resolveInfo.services.addAll(resolvedServices);
    274         resolveInfo.defaultService = null;
    275 
    276         ComponentName defaultComponent = mNextTapComponent;
    277         if (DBG) Log.d(TAG, "resolveAidLocked: next tap component is " + defaultComponent);
    278         Set<String> paymentAids = mCategoryAids.get(CardEmulation.CATEGORY_PAYMENT);
    279         if (paymentAids != null && paymentAids.contains(aid)) {
    280             if (DBG) Log.d(TAG, "resolveAidLocked: AID " + aid + " is a payment AID");
    281             // This AID has been registered as a payment AID by at least one service.
    282             // Get default component for payment if no next tap default.
    283             if (defaultComponent == null) {
    284                 defaultComponent = mCategoryDefaults.get(CardEmulation.CATEGORY_PAYMENT);
    285             }
    286             if (DBG) Log.d(TAG, "resolveAidLocked: default payment component is "
    287                     + defaultComponent);
    288             if (resolvedServices.size() == 1) {
    289                 ApduServiceInfo resolvedService = resolvedServices.get(0);
    290                 if (DBG) Log.d(TAG, "resolveAidLocked: resolved single service " +
    291                         resolvedService.getComponent());
    292                 if (defaultComponent != null &&
    293                         defaultComponent.equals(resolvedService.getComponent())) {
    294                     if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to (default) " +
    295                         resolvedService.getComponent());
    296                     resolveInfo.defaultService = resolvedService;
    297                 } else {
    298                     // So..since we resolved to only one service, and this AID
    299                     // is a payment AID, we know that this service is the only
    300                     // service that has registered for this AID and in fact claimed
    301                     // it was a payment AID.
    302                     // There's two cases:
    303                     // 1. All other AIDs in the payment group are uncontended:
    304                     //    in this case, just route to this app. It won't get
    305                     //    in the way of other apps, and is likely to interact
    306                     //    with different terminal infrastructure anyway.
    307                     // 2. At least one AID in the payment group is contended:
    308                     //    in this case, we should ask the user to confirm,
    309                     //    since it is likely to contend with other apps, even
    310                     //    when touching the same terminal.
    311                     boolean foundConflict = false;
    312                     for (AidGroup aidGroup : resolvedService.getAidGroups()) {
    313                         if (aidGroup.getCategory().equals(CardEmulation.CATEGORY_PAYMENT)) {
    314                             for (String registeredAid : aidGroup.getAids()) {
    315                                 ArrayList<ApduServiceInfo> servicesForAid =
    316                                         mAidToServices.get(registeredAid);
    317                                 if (servicesForAid != null && servicesForAid.size() > 1) {
    318                                     foundConflict = true;
    319                                 }
    320                             }
    321                         }
    322                     }
    323                     if (!foundConflict) {
    324                         if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to " +
    325                             resolvedService.getComponent());
    326                         // Treat this as if it's the default for this AID
    327                         resolveInfo.defaultService = resolvedService;
    328                     } else {
    329                         // Allow this service to handle, but don't set as default
    330                         if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing AID " + aid +
    331                                 " to " + resolvedService.getComponent() +
    332                                 ", but will ask confirmation because its AID group is contended.");
    333                     }
    334                 }
    335             } else if (resolvedServices.size() > 1) {
    336                 // More services have registered. If there's a default and it
    337                 // registered this AID, go with the default. Otherwise, add all.
    338                 if (DBG) Log.d(TAG, "resolveAidLocked: multiple services matched.");
    339                 if (defaultComponent != null) {
    340                     for (ApduServiceInfo service : resolvedServices) {
    341                         if (service.getComponent().equals(defaultComponent)) {
    342                             if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to (default) "
    343                                     + service.getComponent());
    344                             resolveInfo.defaultService = service;
    345                             break;
    346                         }
    347                     }
    348                     if (resolveInfo.defaultService == null) {
    349                         if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to all services");
    350                     }
    351                 }
    352             } // else -> should not hit, we checked for 0 before.
    353         } else {
    354             // This AID is not a payment AID, just return all components
    355             // that can handle it, but be mindful of (next tap) defaults.
    356             for (ApduServiceInfo service : resolvedServices) {
    357                 if (service.getComponent().equals(defaultComponent)) {
    358                     if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: cat OTHER AID, " +
    359                             "routing to (default) " + service.getComponent());
    360                     resolveInfo.defaultService = service;
    361                     break;
    362                 }
    363             }
    364             if (resolveInfo.defaultService == null) {
    365                 // If we didn't find the default, mark the first as default
    366                 // if there is only one.
    367                 if (resolveInfo.services.size() == 1) {
    368                     resolveInfo.defaultService = resolveInfo.services.get(0);
    369                     if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: cat OTHER AID, " +
    370                             "routing to (default) " + resolveInfo.defaultService.getComponent());
    371                 } else {
    372                     if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: cat OTHER AID, routing all");
    373                 }
    374             }
    375         }
    376         return resolveInfo;
    377     }
    378 
    379     void generateAidTreeLocked(List<ApduServiceInfo> services) {
    380         // Easiest is to just build the entire tree again
    381         mAidToServices.clear();
    382         for (ApduServiceInfo service : services) {
    383             if (DBG) Log.d(TAG, "generateAidTree component: " + service.getComponent());
    384             for (String aid : service.getAids()) {
    385                 if (DBG) Log.d(TAG, "generateAidTree AID: " + aid);
    386                 // Check if a mapping exists for this AID
    387                 if (mAidToServices.containsKey(aid)) {
    388                     final ArrayList<ApduServiceInfo> aidServices = mAidToServices.get(aid);
    389                     aidServices.add(service);
    390                 } else {
    391                     final ArrayList<ApduServiceInfo> aidServices =
    392                             new ArrayList<ApduServiceInfo>();
    393                     aidServices.add(service);
    394                     mAidToServices.put(aid, aidServices);
    395                 }
    396             }
    397         }
    398     }
    399 
    400     void generateAidCategoriesLocked(List<ApduServiceInfo> services) {
    401         // Trash existing mapping
    402         mCategoryAids.clear();
    403 
    404         for (ApduServiceInfo service : services) {
    405             ArrayList<AidGroup> aidGroups = service.getAidGroups();
    406             if (aidGroups == null) continue;
    407             for (AidGroup aidGroup : aidGroups) {
    408                 String groupCategory = aidGroup.getCategory();
    409                 Set<String> categoryAids = mCategoryAids.get(groupCategory);
    410                 if (categoryAids == null) {
    411                     categoryAids = new HashSet<String>();
    412                 }
    413                 categoryAids.addAll(aidGroup.getAids());
    414                 mCategoryAids.put(groupCategory, categoryAids);
    415             }
    416         }
    417     }
    418 
    419     boolean updateFromSettingsLocked(int userId) {
    420         // Load current payment default from settings
    421         String name = Settings.Secure.getStringForUser(
    422                 mContext.getContentResolver(), Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
    423                 userId);
    424         ComponentName newDefault = name != null ? ComponentName.unflattenFromString(name) : null;
    425         ComponentName oldDefault = mCategoryDefaults.put(CardEmulation.CATEGORY_PAYMENT,
    426                 newDefault);
    427         if (DBG) Log.d(TAG, "Updating default component to: " + (name != null ?
    428                 ComponentName.unflattenFromString(name) : "null"));
    429         return newDefault != oldDefault;
    430     }
    431 
    432     void generateAidCacheLocked() {
    433         mAidCache.clear();
    434         for (Map.Entry<String, ArrayList<ApduServiceInfo>> aidEntry:
    435                     mAidToServices.entrySet()) {
    436             String aid = aidEntry.getKey();
    437             if (!mAidCache.containsKey(aid)) {
    438                 mAidCache.put(aid, resolveAidLocked(aidEntry.getValue(), aid));
    439             }
    440         }
    441     }
    442 
    443     void updateRoutingLocked() {
    444         if (!mNfcEnabled) {
    445             if (DBG) Log.d(TAG, "Not updating routing table because NFC is off.");
    446             return;
    447         }
    448         final Set<String> handledAids = new HashSet<String>();
    449         // For each AID, find interested services
    450         for (Map.Entry<String, AidResolveInfo> aidEntry:
    451                 mAidCache.entrySet()) {
    452             String aid = aidEntry.getKey();
    453             AidResolveInfo resolveInfo = aidEntry.getValue();
    454             if (resolveInfo.services.size() == 0) {
    455                 // No interested services, if there is a current routing remove it
    456                 mRoutingManager.removeAid(aid);
    457             } else if (resolveInfo.defaultService != null) {
    458                 // There is a default service set, route to that service
    459                 mRoutingManager.setRouteForAid(aid, resolveInfo.defaultService.isOnHost());
    460             } else if (resolveInfo.services.size() == 1) {
    461                 // Only one service, but not the default, must route to host
    462                 // to ask the user to confirm.
    463                 mRoutingManager.setRouteForAid(aid, true);
    464             } else if (resolveInfo.services.size() > 1) {
    465                 // Multiple services, need to route to host to ask
    466                 mRoutingManager.setRouteForAid(aid, true);
    467             }
    468             handledAids.add(aid);
    469         }
    470         // Now, find AIDs in the routing table that are no longer routed to
    471         // and remove them.
    472         Set<String> routedAids = mRoutingManager.getRoutedAids();
    473         for (String aid : routedAids) {
    474             if (!handledAids.contains(aid)) {
    475                 if (DBG) Log.d(TAG, "Removing routing for AID " + aid + ", because " +
    476                         "there are no no interested services.");
    477                 mRoutingManager.removeAid(aid);
    478             }
    479         }
    480         // And commit the routing
    481         mRoutingManager.commitRouting();
    482     }
    483 
    484     void showDefaultRemovedDialog() {
    485         Intent intent = new Intent(mContext, DefaultRemovedActivity.class);
    486         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    487         mContext.startActivityAsUser(intent, UserHandle.CURRENT);
    488     }
    489 
    490     void onPaymentDefaultRemoved(int userId, List<ApduServiceInfo> services) {
    491         int numPaymentServices = 0;
    492         ComponentName lastFoundPaymentService = null;
    493         for (ApduServiceInfo service : services) {
    494             if (service.hasCategory(CardEmulation.CATEGORY_PAYMENT))  {
    495                 numPaymentServices++;
    496                 lastFoundPaymentService = service.getComponent();
    497             }
    498         }
    499         if (DBG) Log.d(TAG, "Number of payment services is " +
    500                 Integer.toString(numPaymentServices));
    501         if (numPaymentServices == 0) {
    502             if (DBG) Log.d(TAG, "Default removed, no services left.");
    503             // No payment services left, unset default and don't ask the user
    504             setDefaultServiceForCategory(userId, null,
    505                     CardEmulation.CATEGORY_PAYMENT);
    506         } else if (numPaymentServices == 1) {
    507             // Only one left, automatically make it the default
    508             if (DBG) Log.d(TAG, "Default removed, making remaining service default.");
    509             setDefaultServiceForCategory(userId, lastFoundPaymentService,
    510                     CardEmulation.CATEGORY_PAYMENT);
    511         } else if (numPaymentServices > 1) {
    512             // More than one left, unset default and ask the user if he wants
    513             // to set a new one
    514             if (DBG) Log.d(TAG, "Default removed, asking user to pick.");
    515             setDefaultServiceForCategory(userId, null,
    516                     CardEmulation.CATEGORY_PAYMENT);
    517             showDefaultRemovedDialog();
    518         }
    519     }
    520 
    521     void setDefaultIfNeededLocked(int userId, List<ApduServiceInfo> services) {
    522         int numPaymentServices = 0;
    523         ComponentName lastFoundPaymentService = null;
    524         for (ApduServiceInfo service : services) {
    525             if (service.hasCategory(CardEmulation.CATEGORY_PAYMENT))  {
    526                 numPaymentServices++;
    527                 lastFoundPaymentService = service.getComponent();
    528             }
    529         }
    530         if (numPaymentServices > 1) {
    531             // More than one service left, leave default unset
    532             if (DBG) Log.d(TAG, "No default set, more than one service left.");
    533         } else if (numPaymentServices == 1) {
    534             // Make single found payment service the default
    535             if (DBG) Log.d(TAG, "No default set, making single service default.");
    536             setDefaultServiceForCategory(userId, lastFoundPaymentService,
    537                     CardEmulation.CATEGORY_PAYMENT);
    538         } else {
    539             // No payment services left, leave default at null
    540             if (DBG) Log.d(TAG, "No default set, last payment service removed.");
    541         }
    542     }
    543 
    544     void checkDefaultsLocked(int userId, List<ApduServiceInfo> services) {
    545         ComponentName defaultPaymentService =
    546                 getDefaultServiceForCategory(userId, CardEmulation.CATEGORY_PAYMENT, false);
    547         if (DBG) Log.d(TAG, "Current default: " + defaultPaymentService);
    548         if (defaultPaymentService != null) {
    549             // Validate the default is still installed and handling payment
    550             ApduServiceInfo serviceInfo = mServiceCache.getService(userId, defaultPaymentService);
    551             if (serviceInfo == null) {
    552                 Log.e(TAG, "Default payment service unexpectedly removed.");
    553                 onPaymentDefaultRemoved(userId, services);
    554             } else if (!serviceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) {
    555                 if (DBG) Log.d(TAG, "Default payment service had payment category removed");
    556                 onPaymentDefaultRemoved(userId, services);
    557             } else {
    558                 // Default still exists and handles the category, nothing do
    559                 if (DBG) Log.d(TAG, "Default payment service still ok.");
    560             }
    561         } else {
    562             // A payment service may have been removed, leaving only one;
    563             // in that case, automatically set that app as default.
    564             setDefaultIfNeededLocked(userId, services);
    565         }
    566         updateFromSettingsLocked(userId);
    567     }
    568 
    569     @Override
    570     public void onServicesUpdated(int userId, List<ApduServiceInfo> services) {
    571         synchronized (mLock) {
    572             if (ActivityManager.getCurrentUser() == userId) {
    573                 // Rebuild our internal data-structures
    574                 checkDefaultsLocked(userId, services);
    575                 generateAidTreeLocked(services);
    576                 generateAidCategoriesLocked(services);
    577                 generateAidCacheLocked();
    578                 updateRoutingLocked();
    579             } else {
    580                 if (DBG) Log.d(TAG, "Ignoring update because it's not for the current user.");
    581             }
    582         }
    583     }
    584 
    585     public void invalidateCache(int currentUser) {
    586         mServiceCache.invalidateCache(currentUser);
    587     }
    588 
    589     public void onNfcDisabled() {
    590         synchronized (mLock) {
    591             mNfcEnabled = false;
    592         }
    593         mServiceCache.onNfcDisabled();
    594         mRoutingManager.onNfccRoutingTableCleared();
    595     }
    596 
    597     public void onNfcEnabled() {
    598         synchronized (mLock) {
    599             mNfcEnabled = true;
    600             updateFromSettingsLocked(ActivityManager.getCurrentUser());
    601         }
    602         mServiceCache.onNfcEnabled();
    603     }
    604 
    605     String dumpEntry(Map.Entry<String, AidResolveInfo> entry) {
    606         StringBuilder sb = new StringBuilder();
    607         sb.append("    \"" + entry.getKey() + "\"\n");
    608         ApduServiceInfo defaultService = entry.getValue().defaultService;
    609         ComponentName defaultComponent = defaultService != null ?
    610                 defaultService.getComponent() : null;
    611 
    612         for (ApduServiceInfo service : entry.getValue().services) {
    613             sb.append("        ");
    614             if (service.getComponent().equals(defaultComponent)) {
    615                 sb.append("*DEFAULT* ");
    616             }
    617             sb.append(service.getComponent() +
    618                     " (Description: " + service.getDescription() + ")\n");
    619         }
    620         return sb.toString();
    621     }
    622 
    623     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    624        mServiceCache.dump(fd, pw, args);
    625        pw.println("AID cache entries: ");
    626        for (Map.Entry<String, AidResolveInfo> entry : mAidCache.entrySet()) {
    627            pw.println(dumpEntry(entry));
    628        }
    629        pw.println("Category defaults: ");
    630        for (Map.Entry<String, ComponentName> entry : mCategoryDefaults.entrySet()) {
    631            pw.println("    " + entry.getKey() + "->" + entry.getValue());
    632        }
    633        pw.println("");
    634     }
    635 }
    636