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