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