1 /*
2  * Copyright (C) 2013 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 package com.android.nfc.cardemulation;
17 
18 import android.sysprop.NfcProperties;
19 import android.util.Log;
20 import android.util.SparseArray;
21 import android.util.proto.ProtoOutputStream;
22 
23 import com.android.nfc.NfcService;
24 import com.android.nfc.NfcStatsLog;
25 
26 import java.io.FileDescriptor;
27 import java.io.PrintWriter;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.Map;
33 import java.util.Objects;
34 import java.util.Set;
35 import androidx.annotation.VisibleForTesting;
36 
37 public class AidRoutingManager {
38 
39     static final String TAG = "AidRoutingManager";
40 
41     static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
42 
43     static final int ROUTE_HOST = 0x00;
44 
45     // Every routing table entry is matched exact
46     static final int AID_MATCHING_EXACT_ONLY = 0x00;
47     // Every routing table entry can be matched either exact or prefix
48     static final int AID_MATCHING_EXACT_OR_PREFIX = 0x01;
49     // Every routing table entry is matched as a prefix
50     static final int AID_MATCHING_PREFIX_ONLY = 0x02;
51     // Every routing table entry can be matched either exact or prefix or subset only
52     static final int AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX = 0x03;
53 
54     int mDefaultIsoDepRoute;
55     //Let mDefaultRoute as default aid route
56     int mDefaultRoute;
57     int mPower_empty_AID = 0x00;
58 
59     int mMaxAidRoutingTableSize;
60 
61     final byte[] mOffHostRouteUicc;
62     final byte[] mOffHostRouteEse;
63     // Used for backward compatibility in case application doesn't specify the
64     // SE
65     final int mDefaultOffHostRoute;
66 
67     // How the NFC controller can match AIDs in the routing table;
68     // see AID_MATCHING constants
69     final int mAidMatchingSupport;
70 
71     final Object mLock = new Object();
72 
73     // mAidRoutingTable contains the current routing table. The index is the route ID.
74     // The route can include routes to a eSE/UICC.
75     SparseArray<Set<String>> mAidRoutingTable =
76             new SparseArray<Set<String>>();
77 
78     // Easy look-up what the route is for a certain AID
79     HashMap<String, Integer> mRouteForAid = new HashMap<String, Integer>();
80     // Easy look-up what the power is for a certain AID
81     HashMap<String, Integer> mPowerForAid = new HashMap<String, Integer>();
82 
83     RoutingOptionManager mRoutingOptionManager = RoutingOptionManager.getInstance();
84     @VisibleForTesting
85     public final class AidEntry {
86         boolean isOnHost;
87         String offHostSE;
88         int route;
89         int aidInfo;
90         int power;
91     }
92 
AidRoutingManager()93     public AidRoutingManager() {
94         mDefaultRoute = mRoutingOptionManager.getDefaultRoute();
95         if (DBG)
96             Log.d(TAG, "mDefaultRoute=0x" + Integer.toHexString(mDefaultRoute));
97         mDefaultOffHostRoute = mRoutingOptionManager.getDefaultOffHostRoute();
98         if (DBG)
99             Log.d(TAG, "mDefaultOffHostRoute=0x" + Integer.toHexString(mDefaultOffHostRoute));
100         mOffHostRouteUicc = mRoutingOptionManager.getOffHostRouteUicc();
101         if (DBG)
102             Log.d(TAG, "mOffHostRouteUicc=" + Arrays.toString(mOffHostRouteUicc));
103         mOffHostRouteEse = mRoutingOptionManager.getOffHostRouteEse();
104         if (DBG)
105           Log.d(TAG, "mOffHostRouteEse=" + Arrays.toString(mOffHostRouteEse));
106         mAidMatchingSupport = mRoutingOptionManager.getAidMatchingSupport();
107         if (DBG)
108             Log.d(TAG, "mAidMatchingSupport=0x" + Integer.toHexString(mAidMatchingSupport));
109         mDefaultIsoDepRoute = mRoutingOptionManager.getDefaultIsoDepRoute();
110         if (DBG)
111             Log.d(TAG, "mDefaultIsoDepRoute=0x" + Integer.toHexString(mDefaultIsoDepRoute));
112     }
113 
supportsAidPrefixRouting()114     public boolean supportsAidPrefixRouting() {
115         return mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX ||
116                 mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY ||
117                  mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX;
118     }
119 
supportsAidSubsetRouting()120     public boolean supportsAidSubsetRouting() {
121         return mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX;
122     }
123 
calculateAidRouteSize(HashMap<String, AidEntry> routeCache)124     public int calculateAidRouteSize(HashMap<String, AidEntry> routeCache) {
125         // TAG + ROUTE + LENGTH_BYTE + POWER
126         int AID_HDR_LENGTH = 0x04;
127         int routeTableSize = 0x00;
128         for(Map.Entry<String, AidEntry> aidEntry : routeCache.entrySet()) {
129             String aid = aidEntry.getKey();
130             // removing prefix length
131             if(aid.endsWith("*")) {
132                 routeTableSize += ((aid.length() - 0x01) / 0x02) + AID_HDR_LENGTH;
133             } else {
134                 routeTableSize += (aid.length() / 0x02)+ AID_HDR_LENGTH;
135             }
136         }
137         if (DBG) Log.d(TAG, "calculateAidRouteSize: " + routeTableSize);
138         return routeTableSize;
139     }
140 
clearNfcRoutingTableLocked()141     private void clearNfcRoutingTableLocked() {
142         for (Map.Entry<String, Integer> aidEntry : mRouteForAid.entrySet())  {
143             String aid = aidEntry.getKey();
144             if (aid.endsWith("*")) {
145                 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
146                     Log.e(TAG, "Device does not support prefix AIDs but AID [" + aid
147                             + "] is registered");
148                 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
149                     if (DBG) Log.d(TAG, "Unrouting prefix AID " + aid);
150                     // Cut off '*' since controller anyway treats all AIDs as a prefix
151                     aid = aid.substring(0, aid.length() - 1);
152                 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX ||
153                     mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
154                     aid = aid.substring(0, aid.length() - 1);
155                     if (DBG) Log.d(TAG, "Unrouting prefix AID " + aid);
156                 }
157             }  else if (aid.endsWith("#")) {
158                 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
159                     Log.e(TAG, "Device does not support subset AIDs but AID [" + aid
160                             + "] is registered");
161                 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY ||
162                     mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) {
163                     Log.e(TAG, "Device does not support subset AIDs but AID [" + aid
164                             + "] is registered");
165                 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
166                     if (DBG) Log.d(TAG, "Unrouting subset AID " + aid);
167                     aid = aid.substring(0, aid.length() - 1);
168                 }
169             } else {
170                 if (DBG) Log.d(TAG, "Unrouting exact AID " + aid);
171             }
172 
173             NfcService.getInstance().unrouteAids(aid);
174         }
175         if (NfcService.getInstance().getNciVersion() >= NfcService.getInstance().NCI_VERSION_2_0) {
176             // unRoute EmptyAid
177             NfcService.getInstance().unrouteAids("");
178         }
179     }
180 
getRouteForSecureElement(String se)181     private int getRouteForSecureElement(String se) {
182         if (se == null || se.length() <= 3) {
183             return 0;
184         }
185         try {
186             if (se.startsWith("eSE") && mOffHostRouteEse != null) {
187                 int index = Integer.parseInt(se.substring(3));
188                 if (mOffHostRouteEse.length >= index && index > 0) {
189                     return mOffHostRouteEse[index - 1] & 0xFF;
190                 }
191             } else if (se.startsWith("SIM") && mOffHostRouteUicc != null) {
192                 int index = Integer.parseInt(se.substring(3));
193                 if (mOffHostRouteUicc.length >= index && index > 0) {
194                     return mOffHostRouteUicc[index - 1] & 0xFF;
195                 }
196             }
197             if (mOffHostRouteEse == null && mOffHostRouteUicc == null)
198               return mDefaultOffHostRoute;
199         } catch (NumberFormatException e) { }
200         return 0;
201     }
202 
203     //Checking in case of power/route update of any AID after conflict
204     //resolution, is routing required or not?
isAidEntryUpdated(HashMap<String, Integer> currRouteForAid, Map.Entry<String, Integer> aidEntry, HashMap<String, Integer> prevPowerForAid)205     private boolean isAidEntryUpdated(HashMap<String, Integer> currRouteForAid,
206                                                 Map.Entry<String, Integer> aidEntry,
207                                                 HashMap<String, Integer> prevPowerForAid) {
208         if(!Objects.equals(currRouteForAid.get(aidEntry.getKey()), aidEntry.getValue()) ||
209             !Objects.equals(
210                 mPowerForAid.get(aidEntry.getKey()),
211                 prevPowerForAid.get(aidEntry.getKey()))) {
212             return true;
213         }
214         return false;
215     }
216 
217     //Check if Any AID entry needs to be removed from previously registered
218     //entries in the Routing table. Current AID entries & power state are part of
219     //mRouteForAid & mPowerForAid respectively. previously registered AID entries &
220     //power states are part of input argument prevRouteForAid & prevPowerForAid respectively.
checkUnrouteAid(HashMap<String, Integer> prevRouteForAid, HashMap<String, Integer> prevPowerForAid)221     private boolean checkUnrouteAid(HashMap<String, Integer> prevRouteForAid,
222                                      HashMap<String, Integer> prevPowerForAid) {
223         for (Map.Entry<String, Integer> aidEntry : prevRouteForAid.entrySet())  {
224             if((aidEntry.getValue() != mDefaultRoute) &&
225                 (!mRouteForAid.containsKey(aidEntry.getKey()) ||
226                 isAidEntryUpdated(mRouteForAid, aidEntry, prevPowerForAid))){
227                     return true;
228             }
229         }
230         return false;
231     }
232 
233     //Check if Any AID entry needs to be added to previously registered
234     //entries in the Routing table. Current AID entries & power state are part of
235     //mRouteForAid & mPowerForAid respectively. previously registered AID entries &
236     //power states are part of input argument prevRouteForAid & prevPowerForAid respectively.
checkRouteAid(HashMap<String, Integer> prevRouteForAid, HashMap<String, Integer> prevPowerForAid)237     private boolean checkRouteAid(HashMap<String, Integer> prevRouteForAid,
238                                    HashMap<String, Integer> prevPowerForAid){
239         for (Map.Entry<String, Integer> aidEntry : mRouteForAid.entrySet())  {
240             if((aidEntry.getValue() != mDefaultRoute) &&
241                 (!prevRouteForAid.containsKey(aidEntry.getKey())||
242                 isAidEntryUpdated(prevRouteForAid, aidEntry, prevPowerForAid))){
243                     return true;
244             }
245         }
246         return false;
247     }
248 
configureRouting(HashMap<String, AidEntry> aidMap, boolean force)249     public boolean configureRouting(HashMap<String, AidEntry> aidMap, boolean force) {
250         boolean aidRouteResolved = false;
251         HashMap<String, AidEntry> aidRoutingTableCache = new HashMap<String, AidEntry>(aidMap.size());
252         ArrayList<Integer> seList = new ArrayList<Integer>();
253         if (mRoutingOptionManager.isRoutingTableOverrided()) {
254             mDefaultRoute = mRoutingOptionManager.getOverrideDefaultRoute();
255         } else {
256             mDefaultRoute = mRoutingOptionManager.getDefaultRoute();
257         }
258         boolean isPowerStateUpdated = false;
259         seList.add(mDefaultRoute);
260         if (mDefaultRoute != ROUTE_HOST) {
261             seList.add(ROUTE_HOST);
262         }
263 
264         SparseArray<Set<String>> aidRoutingTable = new SparseArray<Set<String>>(aidMap.size());
265         HashMap<String, Integer> routeForAid = new HashMap<String, Integer>(aidMap.size());
266         HashMap<String, Integer> powerForAid = new HashMap<String, Integer>(aidMap.size());
267         HashMap<String, Integer> infoForAid = new HashMap<String, Integer>(aidMap.size());
268         HashMap<String, Integer> prevRouteForAid = new HashMap<String, Integer>();
269         HashMap<String, Integer> prevPowerForAid = new HashMap<String, Integer>();
270         // Then, populate internal data structures first
271         for (Map.Entry<String, AidEntry> aidEntry : aidMap.entrySet())  {
272             int route = ROUTE_HOST;
273             if (!aidEntry.getValue().isOnHost) {
274                 String offHostSE = aidEntry.getValue().offHostSE;
275                 if (offHostSE == null) {
276                     route = mDefaultOffHostRoute;
277                 } else {
278                     route = getRouteForSecureElement(offHostSE);
279                     if (route == 0) {
280                         Log.e(TAG, "Invalid Off host Aid Entry " + offHostSE);
281                         continue;
282                     }
283                 }
284             }
285             if (!seList.contains(route))
286                 seList.add(route);
287             aidEntry.getValue().route = route;
288             int aidType = aidEntry.getValue().aidInfo;
289             int power = aidEntry.getValue().power;
290             String aid = aidEntry.getKey();
291             Set<String> entries =
292                     aidRoutingTable.get(route, new HashSet<String>());
293             entries.add(aid);
294             aidRoutingTable.put(route, entries);
295             routeForAid.put(aid, route);
296             powerForAid.put(aid, power);
297             infoForAid.put(aid, aidType);
298         }
299 
300         synchronized (mLock) {
301             if (routeForAid.equals(mRouteForAid) && powerForAid.equals(mPowerForAid) && !force) {
302                 if (DBG) Log.d(TAG, "Routing table unchanged, not updating");
303                 return false;
304             }
305 
306             // Otherwise, update internal structures and commit new routing
307             clearNfcRoutingTableLocked();
308             prevRouteForAid = mRouteForAid;
309             mRouteForAid = routeForAid;
310             prevPowerForAid = mPowerForAid;
311             mPowerForAid = powerForAid;
312             mAidRoutingTable = aidRoutingTable;
313 
314             mMaxAidRoutingTableSize = NfcService.getInstance().getAidRoutingTableSize();
315             if (DBG) Log.d(TAG, "mMaxAidRoutingTableSize: " + mMaxAidRoutingTableSize);
316 
317             //calculate AidRoutingTableSize for existing route destination
318             for(int index = 0; index < seList.size(); index ++) {
319                 mDefaultRoute = seList.get(index);
320                 if(index != 0) {
321                     if (DBG) {
322                         Log.d(TAG, "AidRoutingTable is full, try to switch mDefaultRoute to 0x" + Integer.toHexString(mDefaultRoute));
323                     }
324                 }
325 
326                 aidRoutingTableCache.clear();
327 
328                 if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
329                     /* If a non-default route registers an exact AID which is shorter
330                      * than this exact AID, this will create a problem with controllers
331                      * that treat every AID in the routing table as a prefix.
332                      * For example, if App A registers F0000000041010 as an exact AID,
333                      * and App B registers F000000004 as an exact AID, and App B is not
334                      * the default route, the following would be added to the routing table:
335                      * F000000004 -> non-default destination
336                      * However, because in this mode, the controller treats every routing table
337                      * entry as a prefix, it means F0000000041010 would suddenly go to the non-default
338                      * destination too, whereas it should have gone to the default.
339                      *
340                      * The only way to prevent this is to add the longer AIDs of the
341                      * default route at the top of the table, so they will be matched first.
342                      */
343                     Set<String> defaultRouteAids = mAidRoutingTable.get(mDefaultRoute);
344                     if (defaultRouteAids != null) {
345                         for (String defaultRouteAid : defaultRouteAids) {
346                             // Check whether there are any shorted AIDs routed to non-default
347                             // TODO this is O(N^2) run-time complexity...
348                             for (Map.Entry<String, Integer> aidEntry : mRouteForAid.entrySet()) {
349                                 String aid = aidEntry.getKey();
350                                 int route = aidEntry.getValue();
351                                 if (defaultRouteAid.startsWith(aid) && route != mDefaultRoute) {
352                                     if (DBG) Log.d(TAG, "Adding AID " + defaultRouteAid + " for default " +
353                                             "route, because a conflicting shorter AID will be " +
354                                             "added to the routing table");
355                                     aidRoutingTableCache.put(defaultRouteAid, aidMap.get(defaultRouteAid));
356                                 }
357                             }
358                         }
359                     }
360                 }
361 
362                 // Add AID entries for all non-default routes
363                 for (int i = 0; i < mAidRoutingTable.size(); i++) {
364                     int route = mAidRoutingTable.keyAt(i);
365                     if (route != mDefaultRoute) {
366                         Set<String> aidsForRoute = mAidRoutingTable.get(route);
367                         for (String aid : aidsForRoute) {
368                             if (aid.endsWith("*")) {
369                                 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
370                                     Log.e(TAG, "This device does not support prefix AIDs.");
371                                 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) {
372                                     if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route "
373                                             + Integer.toString(route));
374                                     // Cut off '*' since controller anyway treats all AIDs as a prefix
375                                     aidRoutingTableCache.put(aid.substring(0,aid.length() - 1), aidMap.get(aid));
376                                 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX ||
377                                   mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
378                                     if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route "
379                                             + Integer.toString(route));
380                                     aidRoutingTableCache.put(aid.substring(0,aid.length() - 1), aidMap.get(aid));
381                                 }
382                             } else if (aid.endsWith("#")) {
383                                 if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) {
384                                     Log.e(TAG, "Device does not support subset AIDs but AID [" + aid
385                                             + "] is registered");
386                                 } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY ||
387                                     mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) {
388                                     Log.e(TAG, "Device does not support subset AIDs but AID [" + aid
389                                             + "] is registered");
390                                 } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_SUBSET_OR_PREFIX) {
391                                     if (DBG) Log.d(TAG, "Routing subset AID " + aid + " to route "
392                                             + Integer.toString(route));
393                                     aidRoutingTableCache.put(aid.substring(0,aid.length() - 1), aidMap.get(aid));
394                                 }
395                             } else {
396                                 if (DBG) Log.d(TAG, "Routing exact AID " + aid + " to route "
397                                         + Integer.toString(route));
398                                 aidRoutingTableCache.put(aid, aidMap.get(aid));
399                             }
400                         }
401                     }
402                 }
403 
404                 // register default route in below cases:
405                 // 1. mDefaultRoute is different with mDefaultIsoDepRoute
406                 // 2. mDefaultRoute and mDefaultIsoDepRoute all equal to ROUTE_HOST
407                 //    , which is used for screen off HCE scenarios
408                 if (mDefaultRoute != mDefaultIsoDepRoute || mDefaultIsoDepRoute == ROUTE_HOST) {
409                     if (NfcService.getInstance().getNciVersion()
410                             >= NfcService.getInstance().NCI_VERSION_2_0) {
411                         String emptyAid = "";
412                         AidEntry entry = new AidEntry();
413                         int default_route_power_state;
414                         entry.route = mDefaultRoute;
415                         if (mDefaultRoute == ROUTE_HOST) {
416                             entry.isOnHost = true;
417                             default_route_power_state = RegisteredAidCache.POWER_STATE_SWITCH_ON
418                                     | RegisteredAidCache.POWER_STATE_SCREEN_ON_LOCKED;
419                             Set<String> aidsForDefaultRoute = mAidRoutingTable.get(mDefaultRoute);
420                             if (aidsForDefaultRoute != null) {
421                                 for (String aid : aidsForDefaultRoute) {
422                                     default_route_power_state |= aidMap.get(aid).power;
423                                 }
424                             }
425                         } else {
426                             entry.isOnHost = false;
427                             default_route_power_state = RegisteredAidCache.POWER_STATE_ALL;
428                         }
429                         if(mPower_empty_AID != default_route_power_state)
430                             isPowerStateUpdated = true;
431                         mPower_empty_AID = default_route_power_state;
432                         entry.aidInfo = RegisteredAidCache.AID_ROUTE_QUAL_PREFIX;
433                         entry.power = default_route_power_state;
434 
435                         aidRoutingTableCache.put(emptyAid, entry);
436                         if (DBG) Log.d(TAG, "Add emptyAid into AidRoutingTable");
437                     }
438                 }
439 
440                 // Register additional offhost AIDs when their support power states are
441                 // differernt from the default route entry
442                 if (mDefaultRoute != ROUTE_HOST) {
443                     int default_route_power_state = RegisteredAidCache.POWER_STATE_ALL;
444                     if (NfcService.getInstance().getNciVersion()
445                             < NfcService.getInstance().NCI_VERSION_2_0) {
446                         default_route_power_state =
447                                 RegisteredAidCache.POWER_STATE_ALL_NCI_VERSION_1_0;
448                     }
449 
450                     Set<String> aidsForDefaultRoute = mAidRoutingTable.get(mDefaultRoute);
451                     if (aidsForDefaultRoute != null) {
452                         for (String aid : aidsForDefaultRoute) {
453                             if (aidMap.get(aid).power != default_route_power_state) {
454                                 aidRoutingTableCache.put(aid, aidMap.get(aid));
455                                 isPowerStateUpdated = true;
456                             }
457                         }
458                     }
459                 }
460 
461               if (calculateAidRouteSize(aidRoutingTableCache) <= mMaxAidRoutingTableSize ||
462                     mRoutingOptionManager.isRoutingTableOverrided()) {
463                   aidRouteResolved = true;
464                   break;
465               }
466           }
467 
468             boolean mIsUnrouteRequired = checkUnrouteAid(prevRouteForAid, prevPowerForAid);
469             boolean isRouteTableUpdated = checkRouteAid(prevRouteForAid, prevPowerForAid);
470 
471             if (isPowerStateUpdated || isRouteTableUpdated || mIsUnrouteRequired || force) {
472                 if (aidRouteResolved == true) {
473                     commit(aidRoutingTableCache);
474                 } else {
475                     NfcStatsLog.write(NfcStatsLog.NFC_ERROR_OCCURRED,
476                             NfcStatsLog.NFC_ERROR_OCCURRED__TYPE__AID_OVERFLOW, 0, 0);
477                     Log.e(TAG, "RoutingTable unchanged because it's full, not updating");
478                 }
479             } else {
480                 Log.e(TAG, "All AIDs routing to mDefaultRoute, RoutingTable"
481                         + " update is not required");
482             }
483         }
484         return true;
485     }
486 
commit(HashMap<String, AidEntry> routeCache )487     private void commit(HashMap<String, AidEntry> routeCache ) {
488 
489         if(routeCache != null) {
490 
491             for (Map.Entry<String, AidEntry> aidEntry : routeCache.entrySet())  {
492                 int route = aidEntry.getValue().route;
493                 int aidType = aidEntry.getValue().aidInfo;
494                 String aid = aidEntry.getKey();
495                 int power = aidEntry.getValue().power;
496                 if (DBG) {
497                     Log.d(TAG, "commit aid:" + aid + ",route:" + route
498                         + ",aidtype:" + aidType + ", power state:" + power);
499                 }
500 
501                 NfcService.getInstance().routeAids(aid, route, aidType, power);
502             }
503         }
504 
505         // And finally commit the routing
506         NfcService.getInstance().commitRouting();
507     }
508 
509     /**
510      * This notifies that the AID routing table in the controller
511      * has been cleared (usually due to NFC being turned off).
512      */
onNfccRoutingTableCleared()513     public void onNfccRoutingTableCleared() {
514         // The routing table in the controller was cleared
515         // To stay in sync, clear our own tables.
516         synchronized (mLock) {
517             mAidRoutingTable.clear();
518             mRouteForAid.clear();
519             mPowerForAid.clear();
520         }
521     }
522 
dump(FileDescriptor fd, PrintWriter pw, String[] args)523     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
524         pw.println("Routing table:");
525         pw.println("    Default route: " + ((mDefaultRoute == 0x00) ? "host" : "secure element"));
526         synchronized (mLock) {
527             for (int i = 0; i < mAidRoutingTable.size(); i++) {
528                 Set<String> aids = mAidRoutingTable.valueAt(i);
529                 pw.println("    Routed to 0x" + Integer.toHexString(mAidRoutingTable.keyAt(i)) + ":");
530                 for (String aid : aids) {
531                     pw.println("        \"" + aid + "\"");
532                 }
533             }
534         }
535     }
536 
537     /**
538      * Dump debugging information as a AidRoutingManagerProto
539      *
540      * Note:
541      * See proto definition in frameworks/base/core/proto/android/nfc/card_emulation.proto
542      * When writing a nested message, must call {@link ProtoOutputStream#start(long)} before and
543      * {@link ProtoOutputStream#end(long)} after.
544      * Never reuse a proto field number. When removing a field, mark it as reserved.
545      */
dumpDebug(ProtoOutputStream proto)546     void dumpDebug(ProtoOutputStream proto) {
547         proto.write(AidRoutingManagerProto.DEFAULT_ROUTE, mDefaultRoute);
548         synchronized (mLock) {
549             for (int i = 0; i < mAidRoutingTable.size(); i++) {
550                 long token = proto.start(AidRoutingManagerProto.ROUTES);
551                 proto.write(AidRoutingManagerProto.Route.ID, mAidRoutingTable.keyAt(i));
552                 mAidRoutingTable.valueAt(i).forEach(aid -> {
553                     proto.write(AidRoutingManagerProto.Route.AIDS, aid);
554                 });
555                 proto.end(token);
556             }
557         }
558     }
559 
560     @VisibleForTesting
isRoutingTableCleared()561     public boolean isRoutingTableCleared() {
562         return mAidRoutingTable.size() == 0 && mRouteForAid.isEmpty() && mPowerForAid.isEmpty();
563     }
564 }
565