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
17package com.android.nfc.cardemulation;
18
19import android.app.ActivityManager;
20import android.content.ComponentName;
21import android.content.Context;
22import android.nfc.cardemulation.ApduServiceInfo;
23import android.nfc.cardemulation.CardEmulation;
24import android.util.Log;
25
26import com.google.android.collect.Maps;
27
28import java.io.FileDescriptor;
29import java.io.PrintWriter;
30import java.util.ArrayList;
31import java.util.Collection;
32import java.util.HashMap;
33import java.util.HashSet;
34import java.util.List;
35import java.util.Map;
36import java.util.NavigableMap;
37import java.util.PriorityQueue;
38import java.util.TreeMap;
39
40public class RegisteredAidCache {
41    static final String TAG = "RegisteredAidCache";
42
43    static final boolean DBG = false;
44
45    // mAidServices maps AIDs to services that have registered them.
46    // It's a TreeMap in order to be able to quickly select subsets
47    // of AIDs that conflict with each other.
48    final TreeMap<String, ArrayList<ServiceAidInfo>> mAidServices =
49            new TreeMap<String, ArrayList<ServiceAidInfo>>();
50
51    // mAidCache is a lookup table for quickly mapping an exact or prefix AID to one or
52    // more handling services. It differs from mAidServices in the sense that it
53    // has already accounted for defaults, and hence its return value
54    // is authoritative for the current set of services and defaults.
55    // It is only valid for the current user.
56    final TreeMap<String, AidResolveInfo> mAidCache = new TreeMap<String, AidResolveInfo>();
57
58    // Represents a single AID registration of a service
59    final class ServiceAidInfo {
60        ApduServiceInfo service;
61        String aid;
62        String category;
63
64        @Override
65        public String toString() {
66            return "ServiceAidInfo{" +
67                    "service=" + service.getComponent() +
68                    ", aid='" + aid + '\'' +
69                    ", category='" + category + '\'' +
70                    '}';
71        }
72
73        @Override
74        public boolean equals(Object o) {
75            if (this == o) return true;
76            if (o == null || getClass() != o.getClass()) return false;
77
78            ServiceAidInfo that = (ServiceAidInfo) o;
79
80            if (!aid.equals(that.aid)) return false;
81            if (!category.equals(that.category)) return false;
82            if (!service.equals(that.service)) return false;
83
84            return true;
85        }
86
87        @Override
88        public int hashCode() {
89            int result = service.hashCode();
90            result = 31 * result + aid.hashCode();
91            result = 31 * result + category.hashCode();
92            return result;
93        }
94    }
95
96    // Represents a list of services, an optional default and a category that
97    // an AID was resolved to.
98    final class AidResolveInfo {
99        List<ApduServiceInfo> services = new ArrayList<ApduServiceInfo>();
100        ApduServiceInfo defaultService = null;
101        String category = null;
102        boolean mustRoute = true; // Whether this AID should be routed at all
103
104        @Override
105        public String toString() {
106            return "AidResolveInfo{" +
107                    "services=" + services +
108                    ", defaultService=" + defaultService +
109                    ", category='" + category + '\'' +
110                    ", mustRoute=" + mustRoute +
111                    '}';
112        }
113    }
114
115    final AidResolveInfo EMPTY_RESOLVE_INFO = new AidResolveInfo();
116
117    final Context mContext;
118    final AidRoutingManager mRoutingManager;
119
120    final Object mLock = new Object();
121
122    ComponentName mPreferredPaymentService;
123    ComponentName mPreferredForegroundService;
124
125    boolean mNfcEnabled = false;
126    boolean mSupportsPrefixes = false;
127
128    public RegisteredAidCache(Context context) {
129        mContext = context;
130        mRoutingManager = new AidRoutingManager();
131        mPreferredPaymentService = null;
132        mPreferredForegroundService = null;
133        mSupportsPrefixes = mRoutingManager.supportsAidPrefixRouting();
134        if (mSupportsPrefixes) {
135            if (DBG) Log.d(TAG, "Controller supports AID prefix routing");
136        }
137    }
138
139    public AidResolveInfo resolveAid(String aid) {
140        synchronized (mLock) {
141            if (DBG) Log.d(TAG, "resolveAid: resolving AID " + aid);
142            if (aid.length() < 10) {
143                Log.e(TAG, "AID selected with fewer than 5 bytes.");
144                return EMPTY_RESOLVE_INFO;
145            }
146            AidResolveInfo resolveInfo = new AidResolveInfo();
147            if (mSupportsPrefixes) {
148                // Our AID cache may contain prefixes which also match this AID,
149                // so we must find all potential prefixes and merge the ResolveInfo
150                // of those prefixes plus any exact match in a single result.
151                String shortestAidMatch = aid.substring(0, 10); // Minimum AID length is 5 bytes
152                String longestAidMatch = aid + "*"; // Longest potential matching AID
153
154                if (DBG) Log.d(TAG, "Finding AID registrations in range [" + shortestAidMatch +
155                        " - " + longestAidMatch + "]");
156                NavigableMap<String, AidResolveInfo> matchingAids =
157                        mAidCache.subMap(shortestAidMatch, true, longestAidMatch, true);
158
159                resolveInfo.category = CardEmulation.CATEGORY_OTHER;
160                for (Map.Entry<String, AidResolveInfo> entry : matchingAids.entrySet()) {
161                    boolean isPrefix = isPrefix(entry.getKey());
162                    String entryAid = isPrefix ? entry.getKey().substring(0,
163                            entry.getKey().length() - 1) : entry.getKey(); // Cut off '*' if prefix
164                    if (entryAid.equalsIgnoreCase(aid) || (isPrefix && aid.startsWith(entryAid))) {
165                        if (DBG) Log.d(TAG, "resolveAid: AID " + entry.getKey() + " matches.");
166                        AidResolveInfo entryResolveInfo = entry.getValue();
167                        if (entryResolveInfo.defaultService != null) {
168                            if (resolveInfo.defaultService != null) {
169                                // This shouldn't happen; for every prefix we have only one
170                                // default service.
171                                Log.e(TAG, "Different defaults for conflicting AIDs!");
172                            }
173                            resolveInfo.defaultService = entryResolveInfo.defaultService;
174                            resolveInfo.category = entryResolveInfo.category;
175                        }
176                        for (ApduServiceInfo serviceInfo : entryResolveInfo.services) {
177                            if (!resolveInfo.services.contains(serviceInfo)) {
178                                resolveInfo.services.add(serviceInfo);
179                            }
180                        }
181                    }
182                }
183            } else {
184                resolveInfo = mAidCache.get(aid);
185            }
186            if (DBG) Log.d(TAG, "Resolved to: " + resolveInfo);
187            return resolveInfo;
188        }
189    }
190
191    public boolean supportsAidPrefixRegistration() {
192        return mSupportsPrefixes;
193    }
194
195    public boolean isDefaultServiceForAid(int userId, ComponentName service, String aid) {
196        AidResolveInfo resolveInfo = resolveAid(aid);
197        if (resolveInfo == null || resolveInfo.services == null ||
198                resolveInfo.services.size() == 0) {
199            return false;
200        }
201
202        if (resolveInfo.defaultService != null) {
203            return service.equals(resolveInfo.defaultService.getComponent());
204        } else if (resolveInfo.services.size() == 1) {
205            return service.equals(resolveInfo.services.get(0).getComponent());
206        } else {
207            // More than one service, not the default
208            return false;
209        }
210    }
211
212    /**
213     * Resolves a conflict between multiple services handling the same
214     * AIDs. Note that the AID itself is not an input to the decision
215     * process - the algorithm just looks at the competing services
216     * and what preferences the user has indicated. In short, it works like
217     * this:
218     *
219     * 1) If there is a preferred foreground service, that service wins
220     * 2) Else, if there is a preferred payment service, that service wins
221     * 3) Else, if there is no winner, and all conflicting services will be
222     *    in the list of resolved services.
223     */
224     AidResolveInfo resolveAidConflictLocked(Collection<ServiceAidInfo> conflictingServices,
225                                             boolean makeSingleServiceDefault) {
226        if (conflictingServices == null || conflictingServices.size() == 0) {
227            Log.e(TAG, "resolveAidConflict: No services passed in.");
228            return null;
229        }
230        AidResolveInfo resolveInfo = new AidResolveInfo();
231        resolveInfo.category = CardEmulation.CATEGORY_OTHER;
232
233        ApduServiceInfo matchedForeground = null;
234        ApduServiceInfo matchedPayment = null;
235        for (ServiceAidInfo serviceAidInfo : conflictingServices) {
236            boolean serviceClaimsPaymentAid =
237                    CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category);
238            if (serviceAidInfo.service.getComponent().equals(mPreferredForegroundService)) {
239                resolveInfo.services.add(serviceAidInfo.service);
240                if (serviceClaimsPaymentAid) {
241                    resolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
242                }
243                matchedForeground = serviceAidInfo.service;
244            } else if (serviceAidInfo.service.getComponent().equals(mPreferredPaymentService) &&
245                    serviceClaimsPaymentAid) {
246                resolveInfo.services.add(serviceAidInfo.service);
247                resolveInfo.category = CardEmulation.CATEGORY_PAYMENT;
248                matchedPayment = serviceAidInfo.service;
249            } else {
250                if (serviceClaimsPaymentAid) {
251                    // If this service claims it's a payment AID, don't route it,
252                    // because it's not the default. Otherwise, add it to the list
253                    // but not as default.
254                    if (DBG) Log.d(TAG, "resolveAidLocked: (Ignoring handling service " +
255                            serviceAidInfo.service.getComponent() +
256                            " because it's not the payment default.)");
257                } else {
258                    resolveInfo.services.add(serviceAidInfo.service);
259                }
260            }
261        }
262        if (matchedForeground != null) {
263            // 1st priority: if the foreground app prefers a service,
264            // and that service asks for the AID, that service gets it
265            if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to foreground preferred " +
266                    matchedForeground);
267            resolveInfo.defaultService = matchedForeground;
268        } else if (matchedPayment != null) {
269            // 2nd priority: if there is a preferred payment service,
270            // and that service claims this as a payment AID, that service gets it
271            if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to payment default " +
272                    "default " + matchedPayment);
273            resolveInfo.defaultService = matchedPayment;
274        } else {
275            if (resolveInfo.services.size() == 1 && makeSingleServiceDefault) {
276                if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: making single handling service " +
277                        resolveInfo.services.get(0).getComponent() + " default.");
278                resolveInfo.defaultService = resolveInfo.services.get(0);
279            } else {
280                // Nothing to do, all services already in list
281                if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to all matching services");
282            }
283        }
284        return resolveInfo;
285    }
286
287    class DefaultServiceInfo {
288        ServiceAidInfo paymentDefault;
289        ServiceAidInfo foregroundDefault;
290    }
291
292    DefaultServiceInfo findDefaultServices(ArrayList<ServiceAidInfo> serviceAidInfos) {
293        DefaultServiceInfo defaultServiceInfo = new DefaultServiceInfo();
294
295        for (ServiceAidInfo serviceAidInfo : serviceAidInfos) {
296            boolean serviceClaimsPaymentAid =
297                    CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category);
298            if (serviceAidInfo.service.getComponent().equals(mPreferredForegroundService)) {
299                defaultServiceInfo.foregroundDefault = serviceAidInfo;
300            } else if (serviceAidInfo.service.getComponent().equals(mPreferredPaymentService) &&
301                    serviceClaimsPaymentAid) {
302                defaultServiceInfo.paymentDefault = serviceAidInfo;
303            }
304        }
305        return defaultServiceInfo;
306    }
307
308    AidResolveInfo resolvePrefixAidConflictLocked(ArrayList<ServiceAidInfo> prefixServices,
309                                                  ArrayList<ServiceAidInfo> conflictingServices) {
310        // Find defaults among the prefix services themselves
311        DefaultServiceInfo prefixDefaultInfo = findDefaultServices(prefixServices);
312
313        // Find any defaults among the children
314        DefaultServiceInfo conflictingDefaultInfo = findDefaultServices(conflictingServices);
315
316        // Three conditions under which the prefix root AID gets to be the default
317        // 1. A service registering the prefix root AID is the current foreground preferred
318        // 2. A service registering the prefix root AID is the current tap & pay default AND
319        //    no child is the current foreground preferred
320        // 3. There is only one service for the prefix root AID, and there are no children
321        if (prefixDefaultInfo.foregroundDefault != null) {
322            if (DBG) Log.d(TAG, "Prefix AID service " +
323                    prefixDefaultInfo.foregroundDefault.service.getComponent() + " has foreground" +
324                    " preference, ignoring conflicting AIDs.");
325            // Foreground default trumps any conflicting services, treat as normal AID conflict
326            // and ignore children
327            return resolveAidConflictLocked(prefixServices, true);
328        } else if (prefixDefaultInfo.paymentDefault != null) {
329            // Check if any of the conflicting services is foreground default
330            if (conflictingDefaultInfo.foregroundDefault != null) {
331                // Conflicting AID registration is in foreground, trumps prefix tap&pay default
332                if (DBG) Log.d(TAG, "One of the conflicting AID registrations is foreground " +
333                        "preferred, ignoring prefix.");
334                return EMPTY_RESOLVE_INFO;
335            } else {
336                // Prefix service is tap&pay default, treat as normal AID conflict for just prefix
337                if (DBG) Log.d(TAG, "Prefix AID service " +
338                        prefixDefaultInfo.paymentDefault.service.getComponent() + " is payment" +
339                        " default, ignoring conflicting AIDs.");
340                return resolveAidConflictLocked(prefixServices, true);
341            }
342        } else {
343            if (conflictingDefaultInfo.foregroundDefault != null ||
344                    conflictingDefaultInfo.paymentDefault != null) {
345                if (DBG) Log.d(TAG, "One of the conflicting AID registrations is either payment " +
346                        "default or foreground preferred, ignoring prefix.");
347                return EMPTY_RESOLVE_INFO;
348            } else {
349                // No children that are preferred; add all services of the root
350                // make single service default if no children are present
351                if (DBG) Log.d(TAG, "No service has preference, adding all.");
352                return resolveAidConflictLocked(prefixServices, conflictingServices.isEmpty());
353            }
354        }
355    }
356
357    void generateServiceMapLocked(List<ApduServiceInfo> services) {
358        // Easiest is to just build the entire tree again
359        mAidServices.clear();
360        for (ApduServiceInfo service : services) {
361            if (DBG) Log.d(TAG, "generateServiceMap component: " + service.getComponent());
362            List<String> prefixAids = service.getPrefixAids();
363            for (String aid : service.getAids()) {
364                if (!CardEmulation.isValidAid(aid)) {
365                    Log.e(TAG, "Aid " + aid + " is not valid.");
366                    continue;
367                }
368                if (aid.endsWith("*") && !supportsAidPrefixRegistration()) {
369                    Log.e(TAG, "Prefix AID " + aid + " ignored on device that doesn't support it.");
370                    continue;
371                } else if (supportsAidPrefixRegistration() && prefixAids.size() > 0 && !isPrefix(aid)) {
372                    // Check if we already have an overlapping prefix registered for this AID
373                    boolean foundPrefix = false;
374                    for (String prefixAid : prefixAids) {
375                        String prefix = prefixAid.substring(0, prefixAid.length() - 1);
376                        if (aid.startsWith(prefix)) {
377                            Log.e(TAG, "Ignoring exact AID " + aid + " because prefix AID " + prefixAid +
378                                    " is already registered");
379                            foundPrefix = true;
380                            break;
381                        }
382                    }
383                    if (foundPrefix) {
384                        continue;
385                    }
386                }
387                ServiceAidInfo serviceAidInfo = new ServiceAidInfo();
388                serviceAidInfo.aid = aid.toUpperCase();
389                serviceAidInfo.service = service;
390                serviceAidInfo.category = service.getCategoryForAid(aid);
391
392                if (mAidServices.containsKey(serviceAidInfo.aid)) {
393                    final ArrayList<ServiceAidInfo> serviceAidInfos =
394                            mAidServices.get(serviceAidInfo.aid);
395                    serviceAidInfos.add(serviceAidInfo);
396                } else {
397                    final ArrayList<ServiceAidInfo> serviceAidInfos =
398                            new ArrayList<ServiceAidInfo>();
399                    serviceAidInfos.add(serviceAidInfo);
400                    mAidServices.put(serviceAidInfo.aid, serviceAidInfos);
401                }
402            }
403        }
404    }
405
406    static boolean isPrefix(String aid) {
407        return aid.endsWith("*");
408    }
409
410    final class PrefixConflicts {
411        NavigableMap<String, ArrayList<ServiceAidInfo>> conflictMap;
412        final ArrayList<ServiceAidInfo> services = new ArrayList<ServiceAidInfo>();
413        final HashSet<String> aids = new HashSet<String>();
414    }
415
416    PrefixConflicts findConflictsForPrefixLocked(String prefixAid) {
417        PrefixConflicts prefixConflicts = new PrefixConflicts();
418        String plainAid = prefixAid.substring(0, prefixAid.length() - 1); // Cut off "*"
419        String lastAidWithPrefix = String.format("%-32s", plainAid).replace(' ', 'F');
420        if (DBG) Log.d(TAG, "Finding AIDs in range [" + plainAid + " - " +
421                lastAidWithPrefix + "]");
422        prefixConflicts.conflictMap =
423                mAidServices.subMap(plainAid, true, lastAidWithPrefix, true);
424        for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry :
425                prefixConflicts.conflictMap.entrySet()) {
426            if (!entry.getKey().equalsIgnoreCase(prefixAid)) {
427                if (DBG)
428                    Log.d(TAG, "AID " + entry.getKey() + " conflicts with prefix; " +
429                            " adding handling services for conflict resolution.");
430                prefixConflicts.services.addAll(entry.getValue());
431                prefixConflicts.aids.add(entry.getKey());
432            }
433        }
434        return prefixConflicts;
435    }
436
437    void generateAidCacheLocked() {
438        mAidCache.clear();
439        // Get all exact and prefix AIDs in an ordered list
440        PriorityQueue<String> aidsToResolve = new PriorityQueue<String>(mAidServices.keySet());
441
442        while (!aidsToResolve.isEmpty()) {
443            final ArrayList<String> resolvedAids = new ArrayList<String>();
444
445            String aidToResolve = aidsToResolve.peek();
446            // Because of the lexicographical ordering, all following AIDs either start with the
447            // same bytes and are longer, or start with different bytes.
448
449            // A special case is if another service registered the same AID as a prefix, in
450            // which case we want to start with that AID, since it conflicts with this one
451            if (aidsToResolve.contains(aidToResolve + "*")) {
452                aidToResolve = aidToResolve + "*";
453            }
454            if (DBG) Log.d(TAG, "generateAidCacheLocked: starting with aid " + aidToResolve);
455
456            if (isPrefix(aidToResolve)) {
457                // This AID itself is a prefix; let's consider this prefix as the "root",
458                // and all conflicting AIDs as its children.
459                // For example, if "A000000003*" is the prefix root,
460                // "A000000003", "A00000000301*", "A0000000030102" are all conflicting children AIDs
461                final ArrayList<ServiceAidInfo> prefixServices = new ArrayList<ServiceAidInfo>(
462                        mAidServices.get(aidToResolve));
463
464                // Find all conflicting children services
465                PrefixConflicts prefixConflicts = findConflictsForPrefixLocked(aidToResolve);
466
467                // Resolve conflicts
468                AidResolveInfo resolveInfo = resolvePrefixAidConflictLocked(prefixServices,
469                        prefixConflicts.services);
470                mAidCache.put(aidToResolve, resolveInfo);
471                resolvedAids.add(aidToResolve);
472                if (resolveInfo.defaultService != null) {
473                    // This prefix is the default; therefore, AIDs of all conflicting children
474                    // will no longer be evaluated.
475                    resolvedAids.addAll(prefixConflicts.aids);
476                } else if (resolveInfo.services.size() > 0) {
477                    // This means we don't have a default for this prefix and all its
478                    // conflicting children. So, for all conflicting AIDs, just add
479                    // all handling services without setting a default
480                    boolean foundChildService = false;
481                    for (Map.Entry<String, ArrayList<ServiceAidInfo>> entry :
482                            prefixConflicts.conflictMap.entrySet()) {
483                        if (!entry.getKey().equalsIgnoreCase(aidToResolve)) {
484                            if (DBG)
485                                Log.d(TAG, "AID " + entry.getKey() + " shared with prefix; " +
486                                        " adding all handling services.");
487                            AidResolveInfo childResolveInfo = resolveAidConflictLocked(
488                                    entry.getValue(), false);
489                            // Special case: in this case all children AIDs must be routed to the
490                            // host, so we can ask the user which service is preferred.
491                            // Since these are all "children" of the prefix, they don't need
492                            // to be routed, since the prefix will already get routed to the host
493                            childResolveInfo.mustRoute = false;
494                            mAidCache.put(entry.getKey(),childResolveInfo);
495                            resolvedAids.add(entry.getKey());
496                            foundChildService |= !childResolveInfo.services.isEmpty();
497                        }
498                    }
499                    // Special case: if in the end we didn't add any children services,
500                    // and the prefix has only one service, make that default
501                    if (!foundChildService && resolveInfo.services.size() == 1) {
502                        resolveInfo.defaultService = resolveInfo.services.get(0);
503                    }
504                } else {
505                    // This prefix is not handled at all; we will evaluate
506                    // the children separately in next passes.
507                }
508            } else {
509                // Exact AID and no other conflicting AID registrations present
510                // This is true because aidsToResolve is lexicographically ordered, and
511                // so by necessity all other AIDs are different than this AID or longer.
512                if (DBG) Log.d(TAG, "Exact AID, resolving.");
513                final ArrayList<ServiceAidInfo> conflictingServiceInfos =
514                        new ArrayList<ServiceAidInfo>(mAidServices.get(aidToResolve));
515                mAidCache.put(aidToResolve, resolveAidConflictLocked(conflictingServiceInfos, true));
516                resolvedAids.add(aidToResolve);
517            }
518
519            // Remove the AIDs we resolved from the list of AIDs to resolve
520            if (DBG) Log.d(TAG, "AIDs: " + resolvedAids + " were resolved.");
521            aidsToResolve.removeAll(resolvedAids);
522            resolvedAids.clear();
523        }
524
525        updateRoutingLocked();
526    }
527
528    void updateRoutingLocked() {
529        if (!mNfcEnabled) {
530            if (DBG) Log.d(TAG, "Not updating routing table because NFC is off.");
531            return;
532        }
533        final HashMap<String, Boolean> routingEntries = Maps.newHashMap();
534        // For each AID, find interested services
535        for (Map.Entry<String, AidResolveInfo> aidEntry:
536                mAidCache.entrySet()) {
537            String aid = aidEntry.getKey();
538            AidResolveInfo resolveInfo = aidEntry.getValue();
539            if (!resolveInfo.mustRoute) {
540                if (DBG) Log.d(TAG, "Not routing AID " + aid + " on request.");
541                continue;
542            }
543            if (resolveInfo.services.size() == 0) {
544                // No interested services
545            } else if (resolveInfo.defaultService != null) {
546                // There is a default service set, route to where that service resides -
547                // either on the host (HCE) or on an SE.
548                routingEntries.put(aid, resolveInfo.defaultService.isOnHost());
549            } else if (resolveInfo.services.size() == 1) {
550                // Only one service, but not the default, must route to host
551                // to ask the user to choose one.
552                routingEntries.put(aid, true);
553            } else if (resolveInfo.services.size() > 1) {
554                // Multiple services, need to route to host to ask
555                routingEntries.put(aid, true);
556            }
557        }
558        mRoutingManager.configureRouting(routingEntries);
559    }
560
561    public void onServicesUpdated(int userId, List<ApduServiceInfo> services) {
562        if (DBG) Log.d(TAG, "onServicesUpdated");
563        synchronized (mLock) {
564            if (ActivityManager.getCurrentUser() == userId) {
565                // Rebuild our internal data-structures
566                generateServiceMapLocked(services);
567                generateAidCacheLocked();
568            } else {
569                if (DBG) Log.d(TAG, "Ignoring update because it's not for the current user.");
570            }
571        }
572    }
573
574    public void onPreferredPaymentServiceChanged(ComponentName service) {
575        if (DBG) Log.d(TAG, "Preferred payment service changed.");
576       synchronized (mLock) {
577           mPreferredPaymentService = service;
578           generateAidCacheLocked();
579       }
580    }
581
582    public void onPreferredForegroundServiceChanged(ComponentName service) {
583        if (DBG) Log.d(TAG, "Preferred foreground service changed.");
584        synchronized (mLock) {
585            mPreferredForegroundService = service;
586            generateAidCacheLocked();
587        }
588    }
589
590    public void onNfcDisabled() {
591        synchronized (mLock) {
592            mNfcEnabled = false;
593        }
594        mRoutingManager.onNfccRoutingTableCleared();
595    }
596
597    public void onNfcEnabled() {
598        synchronized (mLock) {
599            mNfcEnabled = true;
600            updateRoutingLocked();
601        }
602    }
603
604    String dumpEntry(Map.Entry<String, AidResolveInfo> entry) {
605        StringBuilder sb = new StringBuilder();
606        String category = entry.getValue().category;
607        ApduServiceInfo defaultServiceInfo = entry.getValue().defaultService;
608        sb.append("    \"" + entry.getKey() + "\" (category: " + category + ")\n");
609        ComponentName defaultComponent = defaultServiceInfo != null ?
610                defaultServiceInfo.getComponent() : null;
611
612        for (ApduServiceInfo serviceInfo : entry.getValue().services) {
613            sb.append("        ");
614            if (serviceInfo.getComponent().equals(defaultComponent)) {
615                sb.append("*DEFAULT* ");
616            }
617            sb.append(serviceInfo.getComponent() +
618                    " (Description: " + serviceInfo.getDescription() + ")\n");
619        }
620        return sb.toString();
621    }
622
623    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
624        pw.println("    AID cache entries: ");
625        for (Map.Entry<String, AidResolveInfo> entry : mAidCache.entrySet()) {
626            pw.println(dumpEntry(entry));
627        }
628        pw.println("    Service preferred by foreground app: " + mPreferredForegroundService);
629        pw.println("    Preferred payment service: " + mPreferredPaymentService);
630        pw.println("");
631        mRoutingManager.dump(fd, pw, args);
632        pw.println("");
633    }
634}
635