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.services == null || resolveInfo.services.size() == 0) return false;
    165 
    166         if (resolveInfo.defaultService != null) {
    167             return service.equals(resolveInfo.defaultService.getComponent());
    168         } else if (resolveInfo.services.size() == 1) {
    169             return service.equals(resolveInfo.services.get(0).getComponent());
    170         } else {
    171             // More than one service, not the default
    172             return false;
    173         }
    174     }
    175 
    176     public boolean setDefaultServiceForCategory(int userId, ComponentName service,
    177             String category) {
    178         if (!CardEmulation.CATEGORY_PAYMENT.equals(category)) {
    179             Log.e(TAG, "Not allowing defaults for category " + category);
    180             return false;
    181         }
    182         synchronized (mLock) {
    183             // TODO Not really nice to be writing to Settings.Secure here...
    184             // ideally we overlay our local changes over whatever is in
    185             // Settings.Secure
    186             if (service == null || mServiceCache.hasService(userId, service)) {
    187                 Settings.Secure.putStringForUser(mContext.getContentResolver(),
    188                         Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
    189                         service != null ? service.flattenToString() : null, userId);
    190             } else {
    191                 Log.e(TAG, "Could not find default service to make default: " + service);
    192             }
    193         }
    194         return true;
    195     }
    196 
    197     public boolean isDefaultServiceForCategory(int userId, String category,
    198             ComponentName service) {
    199         boolean serviceFound = false;
    200         synchronized (mLock) {
    201             // If we don't know about this service yet, it may have just been enabled
    202             // using PackageManager.setComponentEnabledSetting(). The PackageManager
    203             // broadcasts are delayed by 10 seconds in that scenario, which causes
    204             // calls to our APIs referencing that service to fail.
    205             // Hence, update the cache in case we don't know about the service.
    206             serviceFound = mServiceCache.hasService(userId, service);
    207         }
    208         if (!serviceFound) {
    209             if (DBG) Log.d(TAG, "Didn't find passed in service, invalidating cache.");
    210             mServiceCache.invalidateCache(userId);
    211         }
    212         ComponentName defaultService =
    213                 getDefaultServiceForCategory(userId, category, true);
    214         return (defaultService != null && defaultService.equals(service));
    215     }
    216 
    217     ComponentName getDefaultServiceForCategory(int userId, String category,
    218             boolean validateInstalled) {
    219         if (!CardEmulation.CATEGORY_PAYMENT.equals(category)) {
    220             Log.e(TAG, "Not allowing defaults for category " + category);
    221             return null;
    222         }
    223         synchronized (mLock) {
    224             // Load current payment default from settings
    225             String name = Settings.Secure.getStringForUser(
    226                     mContext.getContentResolver(), Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
    227                     userId);
    228             if (name != null) {
    229                 ComponentName service = ComponentName.unflattenFromString(name);
    230                 if (!validateInstalled || service == null) {
    231                     return service;
    232                 } else {
    233                     return mServiceCache.hasService(userId, service) ? service : null;
    234                 }
    235             } else {
    236                 return null;
    237             }
    238         }
    239     }
    240 
    241     public List<ApduServiceInfo> getServicesForCategory(int userId, String category) {
    242         return mServiceCache.getServicesForCategory(userId, category);
    243     }
    244 
    245     public boolean setDefaultForNextTap(int userId, ComponentName service) {
    246         synchronized (mLock) {
    247             if (service != null) {
    248                 mNextTapComponent = service;
    249             } else {
    250                 mNextTapComponent = null;
    251             }
    252             // Update cache and routing table
    253             generateAidCacheLocked();
    254             updateRoutingLocked();
    255         }
    256         return true;
    257     }
    258 
    259     /**
    260      * Resolves an AID to a set of services that can handle it.
    261      */
    262      AidResolveInfo resolveAidLocked(List<ApduServiceInfo> resolvedServices, String aid) {
    263         if (resolvedServices == null || resolvedServices.size() == 0) {
    264             if (DBG) Log.d(TAG, "Could not resolve AID " + aid + " to any service.");
    265             return null;
    266         }
    267         AidResolveInfo resolveInfo = new AidResolveInfo();
    268         if (DBG) Log.d(TAG, "resolveAidLocked: resolving AID " + aid);
    269         resolveInfo.services = new ArrayList<ApduServiceInfo>();
    270         resolveInfo.services.addAll(resolvedServices);
    271         resolveInfo.defaultService = null;
    272 
    273         ComponentName defaultComponent = mNextTapComponent;
    274         if (DBG) Log.d(TAG, "resolveAidLocked: next tap component is " + defaultComponent);
    275         Set<String> paymentAids = mCategoryAids.get(CardEmulation.CATEGORY_PAYMENT);
    276         if (paymentAids != null && paymentAids.contains(aid)) {
    277             if (DBG) Log.d(TAG, "resolveAidLocked: AID " + aid + " is a payment AID");
    278             // This AID has been registered as a payment AID by at least one service.
    279             // Get default component for payment if no next tap default.
    280             if (defaultComponent == null) {
    281                 defaultComponent = mCategoryDefaults.get(CardEmulation.CATEGORY_PAYMENT);
    282             }
    283             if (DBG) Log.d(TAG, "resolveAidLocked: default payment component is "
    284                     + defaultComponent);
    285             if (resolvedServices.size() == 1) {
    286                 ApduServiceInfo resolvedService = resolvedServices.get(0);
    287                 if (DBG) Log.d(TAG, "resolveAidLocked: resolved single service " +
    288                         resolvedService.getComponent());
    289                 if (defaultComponent != null &&
    290                         defaultComponent.equals(resolvedService.getComponent())) {
    291                     if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to (default) " +
    292                         resolvedService.getComponent());
    293                     resolveInfo.defaultService = resolvedService;
    294                 } else {
    295                     // So..since we resolved to only one service, and this AID
    296                     // is a payment AID, we know that this service is the only
    297                     // service that has registered for this AID and in fact claimed
    298                     // it was a payment AID.
    299                     // There's two cases:
    300                     // 1. All other AIDs in the payment group are uncontended:
    301                     //    in this case, just route to this app. It won't get
    302                     //    in the way of other apps, and is likely to interact
    303                     //    with different terminal infrastructure anyway.
    304                     // 2. At least one AID in the payment group is contended:
    305                     //    in this case, we should ask the user to confirm,
    306                     //    since it is likely to contend with other apps, even
    307                     //    when touching the same terminal.
    308                     boolean foundConflict = false;
    309                     for (AidGroup aidGroup : resolvedService.getAidGroups()) {
    310                         if (aidGroup.getCategory().equals(CardEmulation.CATEGORY_PAYMENT)) {
    311                             for (String registeredAid : aidGroup.getAids()) {
    312                                 ArrayList<ApduServiceInfo> servicesForAid =
    313                                         mAidToServices.get(registeredAid);
    314                                 if (servicesForAid != null && servicesForAid.size() > 1) {
    315                                     foundConflict = true;
    316                                 }
    317                             }
    318                         }
    319                     }
    320                     if (!foundConflict) {
    321                         if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to " +
    322                             resolvedService.getComponent());
    323                         // Treat this as if it's the default for this AID
    324                         resolveInfo.defaultService = resolvedService;
    325                     } else {
    326                         // Allow this service to handle, but don't set as default
    327                         if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing AID " + aid +
    328                                 " to " + resolvedService.getComponent() +
    329                                 ", but will ask confirmation because its AID group is contended.");
    330                     }
    331                 }
    332             } else if (resolvedServices.size() > 1) {
    333                 // More services have registered. If there's a default and it
    334                 // registered this AID, go with the default. Otherwise, add all.
    335                 if (DBG) Log.d(TAG, "resolveAidLocked: multiple services matched.");
    336                 if (defaultComponent != null) {
    337                     for (ApduServiceInfo service : resolvedServices) {
    338                         if (service.getComponent().equals(defaultComponent)) {
    339                             if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to (default) "
    340                                     + service.getComponent());
    341                             resolveInfo.defaultService = service;
    342                             break;
    343                         }
    344                     }
    345                     if (resolveInfo.defaultService == null) {
    346                         if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to all services");
    347                     }
    348                 }
    349             } // else -> should not hit, we checked for 0 before.
    350         } else {
    351             // This AID is not a payment AID, just return all components
    352             // that can handle it, but be mindful of (next tap) defaults.
    353             for (ApduServiceInfo service : resolvedServices) {
    354                 if (service.getComponent().equals(defaultComponent)) {
    355                     if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: cat OTHER AID, " +
    356                             "routing to (default) " + service.getComponent());
    357                     resolveInfo.defaultService = service;
    358                     break;
    359                 }
    360             }
    361             if (resolveInfo.defaultService == null) {
    362                 // If we didn't find the default, mark the first as default
    363                 // if there is only one.
    364                 if (resolveInfo.services.size() == 1) {
    365                     resolveInfo.defaultService = resolveInfo.services.get(0);
    366                     if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: cat OTHER AID, " +
    367                             "routing to (default) " + resolveInfo.defaultService.getComponent());
    368                 } else {
    369                     if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: cat OTHER AID, routing all");
    370                 }
    371             }
    372         }
    373         return resolveInfo;
    374     }
    375 
    376     void generateAidTreeLocked(List<ApduServiceInfo> services) {
    377         // Easiest is to just build the entire tree again
    378         mAidToServices.clear();
    379         for (ApduServiceInfo service : services) {
    380             if (DBG) Log.d(TAG, "generateAidTree component: " + service.getComponent());
    381             for (String aid : service.getAids()) {
    382                 if (DBG) Log.d(TAG, "generateAidTree AID: " + aid);
    383                 // Check if a mapping exists for this AID
    384                 if (mAidToServices.containsKey(aid)) {
    385                     final ArrayList<ApduServiceInfo> aidServices = mAidToServices.get(aid);
    386                     aidServices.add(service);
    387                 } else {
    388                     final ArrayList<ApduServiceInfo> aidServices =
    389                             new ArrayList<ApduServiceInfo>();
    390                     aidServices.add(service);
    391                     mAidToServices.put(aid, aidServices);
    392                 }
    393             }
    394         }
    395     }
    396 
    397     void generateAidCategoriesLocked(List<ApduServiceInfo> services) {
    398         // Trash existing mapping
    399         mCategoryAids.clear();
    400 
    401         for (ApduServiceInfo service : services) {
    402             ArrayList<AidGroup> aidGroups = service.getAidGroups();
    403             if (aidGroups == null) continue;
    404             for (AidGroup aidGroup : aidGroups) {
    405                 String groupCategory = aidGroup.getCategory();
    406                 Set<String> categoryAids = mCategoryAids.get(groupCategory);
    407                 if (categoryAids == null) {
    408                     categoryAids = new HashSet<String>();
    409                 }
    410                 categoryAids.addAll(aidGroup.getAids());
    411                 mCategoryAids.put(groupCategory, categoryAids);
    412             }
    413         }
    414     }
    415 
    416     boolean updateFromSettingsLocked(int userId) {
    417         // Load current payment default from settings
    418         String name = Settings.Secure.getStringForUser(
    419                 mContext.getContentResolver(), Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
    420                 userId);
    421         ComponentName newDefault = name != null ? ComponentName.unflattenFromString(name) : null;
    422         ComponentName oldDefault = mCategoryDefaults.put(CardEmulation.CATEGORY_PAYMENT,
    423                 newDefault);
    424         if (DBG) Log.d(TAG, "Updating default component to: " + (name != null ?
    425                 ComponentName.unflattenFromString(name) : "null"));
    426         return newDefault != oldDefault;
    427     }
    428 
    429     void generateAidCacheLocked() {
    430         mAidCache.clear();
    431         for (Map.Entry<String, ArrayList<ApduServiceInfo>> aidEntry:
    432                     mAidToServices.entrySet()) {
    433             String aid = aidEntry.getKey();
    434             if (!mAidCache.containsKey(aid)) {
    435                 mAidCache.put(aid, resolveAidLocked(aidEntry.getValue(), aid));
    436             }
    437         }
    438     }
    439 
    440     void updateRoutingLocked() {
    441         if (!mNfcEnabled) {
    442             if (DBG) Log.d(TAG, "Not updating routing table because NFC is off.");
    443             return;
    444         }
    445         final Set<String> handledAids = new HashSet<String>();
    446         // For each AID, find interested services
    447         for (Map.Entry<String, AidResolveInfo> aidEntry:
    448                 mAidCache.entrySet()) {
    449             String aid = aidEntry.getKey();
    450             AidResolveInfo resolveInfo = aidEntry.getValue();
    451             if (resolveInfo.services.size() == 0) {
    452                 // No interested services, if there is a current routing remove it
    453                 mRoutingManager.removeAid(aid);
    454             } else if (resolveInfo.defaultService != null) {
    455                 // There is a default service set, route to that service
    456                 mRoutingManager.setRouteForAid(aid, resolveInfo.defaultService.isOnHost());
    457             } else if (resolveInfo.services.size() == 1) {
    458                 // Only one service, but not the default, must route to host
    459                 // to ask the user to confirm.
    460                 mRoutingManager.setRouteForAid(aid, true);
    461             } else if (resolveInfo.services.size() > 1) {
    462                 // Multiple services, need to route to host to ask
    463                 mRoutingManager.setRouteForAid(aid, true);
    464             }
    465             handledAids.add(aid);
    466         }
    467         // Now, find AIDs in the routing table that are no longer routed to
    468         // and remove them.
    469         Set<String> routedAids = mRoutingManager.getRoutedAids();
    470         for (String aid : routedAids) {
    471             if (!handledAids.contains(aid)) {
    472                 if (DBG) Log.d(TAG, "Removing routing for AID " + aid + ", because " +
    473                         "there are no no interested services.");
    474                 mRoutingManager.removeAid(aid);
    475             }
    476         }
    477         // And commit the routing
    478         mRoutingManager.commitRouting();
    479     }
    480 
    481     void showDefaultRemovedDialog() {
    482         Intent intent = new Intent(mContext, DefaultRemovedActivity.class);
    483         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    484         mContext.startActivityAsUser(intent, UserHandle.CURRENT);
    485     }
    486 
    487     void onPaymentDefaultRemoved(int userId, List<ApduServiceInfo> services) {
    488         int numPaymentServices = 0;
    489         ComponentName lastFoundPaymentService = null;
    490         for (ApduServiceInfo service : services) {
    491             if (service.hasCategory(CardEmulation.CATEGORY_PAYMENT))  {
    492                 numPaymentServices++;
    493                 lastFoundPaymentService = service.getComponent();
    494             }
    495         }
    496         if (DBG) Log.d(TAG, "Number of payment services is " +
    497                 Integer.toString(numPaymentServices));
    498         if (numPaymentServices == 0) {
    499             if (DBG) Log.d(TAG, "Default removed, no services left.");
    500             // No payment services left, unset default and don't ask the user
    501             setDefaultServiceForCategory(userId, null,
    502                     CardEmulation.CATEGORY_PAYMENT);
    503         } else if (numPaymentServices == 1) {
    504             // Only one left, automatically make it the default
    505             if (DBG) Log.d(TAG, "Default removed, making remaining service default.");
    506             setDefaultServiceForCategory(userId, lastFoundPaymentService,
    507                     CardEmulation.CATEGORY_PAYMENT);
    508         } else if (numPaymentServices > 1) {
    509             // More than one left, unset default and ask the user if he wants
    510             // to set a new one
    511             if (DBG) Log.d(TAG, "Default removed, asking user to pick.");
    512             setDefaultServiceForCategory(userId, null,
    513                     CardEmulation.CATEGORY_PAYMENT);
    514             showDefaultRemovedDialog();
    515         }
    516     }
    517 
    518     void setDefaultIfNeededLocked(int userId, List<ApduServiceInfo> services) {
    519         int numPaymentServices = 0;
    520         ComponentName lastFoundPaymentService = null;
    521         for (ApduServiceInfo service : services) {
    522             if (service.hasCategory(CardEmulation.CATEGORY_PAYMENT))  {
    523                 numPaymentServices++;
    524                 lastFoundPaymentService = service.getComponent();
    525             }
    526         }
    527         if (numPaymentServices > 1) {
    528             // More than one service left, leave default unset
    529             if (DBG) Log.d(TAG, "No default set, more than one service left.");
    530         } else if (numPaymentServices == 1) {
    531             // Make single found payment service the default
    532             if (DBG) Log.d(TAG, "No default set, making single service default.");
    533             setDefaultServiceForCategory(userId, lastFoundPaymentService,
    534                     CardEmulation.CATEGORY_PAYMENT);
    535         } else {
    536             // No payment services left, leave default at null
    537             if (DBG) Log.d(TAG, "No default set, last payment service removed.");
    538         }
    539     }
    540 
    541     void checkDefaultsLocked(int userId, List<ApduServiceInfo> services) {
    542         ComponentName defaultPaymentService =
    543                 getDefaultServiceForCategory(userId, CardEmulation.CATEGORY_PAYMENT, false);
    544         if (DBG) Log.d(TAG, "Current default: " + defaultPaymentService);
    545         if (defaultPaymentService != null) {
    546             // Validate the default is still installed and handling payment
    547             ApduServiceInfo serviceInfo = mServiceCache.getService(userId, defaultPaymentService);
    548             if (serviceInfo == null) {
    549                 Log.e(TAG, "Default payment service unexpectedly removed.");
    550                 onPaymentDefaultRemoved(userId, services);
    551             } else if (!serviceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) {
    552                 if (DBG) Log.d(TAG, "Default payment service had payment category removed");
    553                 onPaymentDefaultRemoved(userId, services);
    554             } else {
    555                 // Default still exists and handles the category, nothing do
    556                 if (DBG) Log.d(TAG, "Default payment service still ok.");
    557             }
    558         } else {
    559             // A payment service may have been removed, leaving only one;
    560             // in that case, automatically set that app as default.
    561             setDefaultIfNeededLocked(userId, services);
    562         }
    563         updateFromSettingsLocked(userId);
    564     }
    565 
    566     @Override
    567     public void onServicesUpdated(int userId, List<ApduServiceInfo> services) {
    568         synchronized (mLock) {
    569             if (ActivityManager.getCurrentUser() == userId) {
    570                 // Rebuild our internal data-structures
    571                 checkDefaultsLocked(userId, services);
    572                 generateAidTreeLocked(services);
    573                 generateAidCategoriesLocked(services);
    574                 generateAidCacheLocked();
    575                 updateRoutingLocked();
    576             } else {
    577                 if (DBG) Log.d(TAG, "Ignoring update because it's not for the current user.");
    578             }
    579         }
    580     }
    581 
    582     public void invalidateCache(int currentUser) {
    583         mServiceCache.invalidateCache(currentUser);
    584     }
    585 
    586     public void onNfcDisabled() {
    587         synchronized (mLock) {
    588             mNfcEnabled = false;
    589         }
    590         mServiceCache.onNfcDisabled();
    591         mRoutingManager.onNfccRoutingTableCleared();
    592     }
    593 
    594     public void onNfcEnabled() {
    595         synchronized (mLock) {
    596             mNfcEnabled = true;
    597             updateFromSettingsLocked(ActivityManager.getCurrentUser());
    598         }
    599         mServiceCache.onNfcEnabled();
    600     }
    601 
    602     String dumpEntry(Map.Entry<String, AidResolveInfo> entry) {
    603         StringBuilder sb = new StringBuilder();
    604         sb.append("    \"" + entry.getKey() + "\"\n");
    605         ApduServiceInfo defaultService = entry.getValue().defaultService;
    606         ComponentName defaultComponent = defaultService != null ?
    607                 defaultService.getComponent() : null;
    608 
    609         for (ApduServiceInfo service : entry.getValue().services) {
    610             sb.append("        ");
    611             if (service.getComponent().equals(defaultComponent)) {
    612                 sb.append("*DEFAULT* ");
    613             }
    614             sb.append(service.getComponent() +
    615                     " (Description: " + service.getDescription() + ")\n");
    616         }
    617         return sb.toString();
    618     }
    619 
    620     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    621        mServiceCache.dump(fd, pw, args);
    622        pw.println("AID cache entries: ");
    623        for (Map.Entry<String, AidResolveInfo> entry : mAidCache.entrySet()) {
    624            pw.println(dumpEntry(entry));
    625        }
    626        pw.println("Category defaults: ");
    627        for (Map.Entry<String, ComponentName> entry : mCategoryDefaults.entrySet()) {
    628            pw.println("    " + entry.getKey() + "->" + entry.getValue());
    629        }
    630        pw.println("");
    631     }
    632 }
    633