• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.content.Context;
19 import android.content.SharedPreferences;
20 import android.content.pm.PackageManager;
21 import android.sysprop.NfcProperties;
22 import android.text.TextUtils;
23 import android.util.Log;
24 
25 import androidx.annotation.VisibleForTesting;
26 
27 import com.android.nfc.DeviceConfigFacade;
28 import com.android.nfc.NfcService;
29 import com.android.nfc.cardemulation.util.TelephonyUtils;
30 import com.android.nfc.dhimpl.NativeNfcManager;
31 
32 import java.util.Arrays;
33 import java.util.HashMap;
34 import java.util.Map;
35 import java.util.Optional;
36 
37 public class RoutingOptionManager {
38     static final String TAG = "RoutingOptionManager";
39     static final boolean DBG = NfcProperties.debug_enabled().orElse(true);
40 
41     static final int ROUTE_UNKNOWN = -1;
42     static final int ROUTE_DEFAULT = -2;
43 
44     public static final String DEVICE_HOST = "DH";
45     public static final String SE_NDEF_NFCEE = "NDEF-NFCEE";
46     public static final String SE_PREFIX_SIM = "SIM";
47     public static final String SE_PREFIX_ESE = "eSE";
48 
49     public static final String PREF_ROUTING_OPTIONS = "RoutingOptionPrefs";
50     public static final String KEY_DEFAULT_ROUTE = "default_route";
51     public static final String KEY_DEFAULT_ISO_DEP_ROUTE = "default_iso_dep_route";
52     public static final String KEY_DEFAULT_OFFHOST_ROUTE = "default_offhost_route";
53     public static final String KEY_DEFAULT_SC_ROUTE = "default_sc_route";
54     public static final String KEY_AUTO_CHANGE_CAPABLE = "allow_auto_routing_changed";
55     Context mContext;
56     private SharedPreferences mPrefs;
57 
58     int mDefaultRoute;
59     int mDefaultIsoDepRoute;
60     int mDefaultOffHostRoute;
61     int mDefaultFelicaRoute;
62     int mDefaultScRoute;
63     int mNdefNfceeRoute;
64     final byte[] mOffHostRouteUicc;
65     final byte[] mOffHostRouteEse;
66     final int mAidMatchingSupport;
67 
68     int mOverrideDefaultRoute = ROUTE_UNKNOWN;
69     int mOverrideDefaultIsoDepRoute = ROUTE_UNKNOWN;
70     int mOverrideDefaultOffHostRoute = ROUTE_UNKNOWN;
71     int mOverrideDefaultFelicaRoute = ROUTE_UNKNOWN;
72     int mOverrideDefaultScRoute = ROUTE_UNKNOWN;
73 
74     boolean mIsRoutingTableOverrided = false;
75 
76     boolean mIsAutoChangeCapable = true;
77     boolean mIsUiccCapable = false;
78     boolean mIsEseCapable = false;
79 
80     // Look up table for secure element name to route id
81     HashMap<String, Integer> mRouteForSecureElement = new HashMap<>();
82 
83     // Look up table for route id to secure element name
84     HashMap<Integer, String> mSecureElementForRoute = new HashMap<>();
85 
86     // Helper class for SIM routing values
87     static class SimSettings {
88         int type;
89         int index;
90         // Up to two eUICCs can be supported depending on whether MEP is supported or not.
91         int[] eUiccIndexs = new int[2];
SimSettings(int numberOfUicc, int startIndexOfEuicc)92         SimSettings(int numberOfUicc, int startIndexOfEuicc) {
93             type = TelephonyUtils.SIM_TYPE_UNKNOWN;
94             index = 0;
95             if (numberOfUicc != 0 && numberOfUicc > startIndexOfEuicc) {
96                 eUiccIndexs[0] = startIndexOfEuicc;
97                 eUiccIndexs[1] = (numberOfUicc > startIndexOfEuicc + 1) ?
98                         startIndexOfEuicc + 1 : startIndexOfEuicc;
99             }
100         }
getName()101         String getName() {
102             return SE_PREFIX_SIM + (index + 1);
103         }
setType(int type)104         void setType(int type) {
105             this.type = type;
106             setIndex();
107         }
setIndex()108         private void setIndex() {
109             switch (type) {
110                 case TelephonyUtils.SIM_TYPE_UICC:
111                     index = 0;
112                     break;
113                 case TelephonyUtils.SIM_TYPE_EUICC_1:
114                     index = eUiccIndexs[0];
115                     break;
116                 case TelephonyUtils.SIM_TYPE_EUICC_2:
117                     index = eUiccIndexs[1];
118                     break;
119                 default:
120                     break;
121             }
122         }
123     }
124 
125     SimSettings mPreferredSimSettings;
126 
127     int mMepMode;
128     @VisibleForTesting
doGetDefaultRouteDestination()129     native int doGetDefaultRouteDestination();
130     @VisibleForTesting
doGetDefaultIsoDepRouteDestination()131     native int doGetDefaultIsoDepRouteDestination();
132     @VisibleForTesting
doGetDefaultOffHostRouteDestination()133     native int doGetDefaultOffHostRouteDestination();
134     @VisibleForTesting
doGetDefaultFelicaRouteDestination()135     native int doGetDefaultFelicaRouteDestination();
136     @VisibleForTesting
doGetDefaultScRouteDestination()137     native int doGetDefaultScRouteDestination();
138     @VisibleForTesting
doGetOffHostUiccDestination()139     native byte[] doGetOffHostUiccDestination();
140     @VisibleForTesting
doGetOffHostEseDestination()141     native byte[] doGetOffHostEseDestination();
142     @VisibleForTesting
doGetAidMatchingMode()143     native int doGetAidMatchingMode();
doGetEuiccMepMode()144     native int doGetEuiccMepMode();
145 
146     private static RoutingOptionManager sInstance;
147 
getInstance()148     public static RoutingOptionManager getInstance() {
149         if (sInstance == null) {
150             sInstance = new RoutingOptionManager();
151         }
152         return sInstance;
153     }
154 
155     @VisibleForTesting
RoutingOptionManager()156     RoutingOptionManager() {
157         mDefaultRoute = doGetDefaultRouteDestination();
158         if (DBG) Log.d(TAG, "mDefaultRoute=0x" + Integer.toHexString(mDefaultRoute));
159         mDefaultIsoDepRoute = doGetDefaultIsoDepRouteDestination();
160         if (DBG) Log.d(TAG, "mDefaultIsoDepRoute=0x" + Integer.toHexString(mDefaultIsoDepRoute));
161         mDefaultOffHostRoute = doGetDefaultOffHostRouteDestination();
162         if (DBG) Log.d(TAG, "mDefaultOffHostRoute=0x" + Integer.toHexString(mDefaultOffHostRoute));
163         mDefaultFelicaRoute = doGetDefaultFelicaRouteDestination();
164         if (DBG) Log.d(TAG, "mDefaultFelicaRoute=0x" + Integer.toHexString(mDefaultFelicaRoute));
165         mDefaultScRoute = doGetDefaultScRouteDestination();
166         if (DBG) Log.d(TAG, "mDefaultScRoute=0x" + Integer.toHexString(mDefaultScRoute));
167         mOffHostRouteUicc = doGetOffHostUiccDestination();
168         if (DBG) Log.d(TAG, "mOffHostRouteUicc=" + Arrays.toString(mOffHostRouteUicc));
169         mOffHostRouteEse = doGetOffHostEseDestination();
170         if (DBG) Log.d(TAG, "mOffHostRouteEse=" + Arrays.toString(mOffHostRouteEse));
171         mAidMatchingSupport = doGetAidMatchingMode();
172         if (DBG) Log.d(TAG, "mAidMatchingSupport=0x" + Integer.toHexString(mAidMatchingSupport));
173         mNdefNfceeRoute = NativeNfcManager.getInstance().getNdefNfceeRouteId();
174         if (DBG) Log.d(TAG, "mNdefNfceeRoute=0x" + Integer.toHexString(mNdefNfceeRoute));
175 
176         mPreferredSimSettings = new SimSettings((mOffHostRouteUicc != null) ?
177                 mOffHostRouteUicc.length : 0, 1);
178         mMepMode = doGetEuiccMepMode();
179         createLookUpTable();
180     }
181 
overwriteRoutingTable()182     public void overwriteRoutingTable() {
183         Log.d(TAG, "overwriteRoutingTable()");
184         if (mOverrideDefaultRoute != ROUTE_UNKNOWN) {
185             if (mOverrideDefaultRoute == ROUTE_DEFAULT) {
186                 Log.i(TAG, "overwriteRoutingTable: overwrite mDefaultRoute with "
187                         + "default config value");
188                 mDefaultRoute = doGetDefaultRouteDestination();
189             } else {
190                 Log.d(TAG, "overwriteRoutingTable: mDefaultRoute : "
191                     + Integer.toHexString(mOverrideDefaultRoute));
192                 mDefaultRoute = mOverrideDefaultRoute;
193             }
194             writeRoutingOption(KEY_DEFAULT_ROUTE, getSecureElementForRoute(mDefaultRoute));
195         }
196 
197         if (mOverrideDefaultIsoDepRoute != ROUTE_UNKNOWN) {
198             if (mOverrideDefaultIsoDepRoute == ROUTE_DEFAULT) {
199                 Log.i(TAG, "overwriteRoutingTable: overwrite mDefaultIsoDepRoute "
200                         + "with default config value");
201                 mDefaultIsoDepRoute = doGetDefaultIsoDepRouteDestination();
202             } else {
203                 Log.d(TAG, "overwriteRoutingTable: mDefaultIsoDepRoute : "
204                         + Integer.toHexString(mOverrideDefaultIsoDepRoute));
205                 mDefaultIsoDepRoute = mOverrideDefaultIsoDepRoute;
206             }
207             writeRoutingOption(
208                     KEY_DEFAULT_ISO_DEP_ROUTE, getSecureElementForRoute(mDefaultIsoDepRoute));
209         }
210 
211         if (mOverrideDefaultOffHostRoute != ROUTE_UNKNOWN) {
212             if (mOverrideDefaultOffHostRoute == ROUTE_DEFAULT) {
213                 Log.i(TAG, "overwriteRoutingTable: overwrite mDefaultOffHostRoute with "
214                         + "default config value");
215                 mDefaultOffHostRoute = doGetDefaultOffHostRouteDestination();
216             } else {
217                 Log.d(TAG, "overwriteRoutingTable: mDefaultOffHostRoute : "
218                         + Integer.toHexString(mOverrideDefaultOffHostRoute));
219                 mDefaultOffHostRoute = mOverrideDefaultOffHostRoute;
220             }
221             writeRoutingOption(
222                     KEY_DEFAULT_OFFHOST_ROUTE, getSecureElementForRoute(mDefaultOffHostRoute));
223         }
224 
225         if (mOverrideDefaultScRoute != ROUTE_UNKNOWN) {
226             if (mOverrideDefaultScRoute == ROUTE_DEFAULT) {
227                 Log.i(TAG, "overwriteRoutingTable: mDefaultScRoute with default config value");
228                 mDefaultScRoute = doGetDefaultScRouteDestination();
229             } else {
230                 Log.d(TAG, "overwriteRoutingTable: mDefaultScRoute : "
231                             + Integer.toHexString(mOverrideDefaultScRoute));
232                 mDefaultScRoute = mOverrideDefaultScRoute;
233             }
234             writeRoutingOption(
235                     KEY_DEFAULT_SC_ROUTE, getSecureElementForRoute(mDefaultScRoute));
236         }
237 
238         mOverrideDefaultRoute = mOverrideDefaultIsoDepRoute = mOverrideDefaultOffHostRoute =
239                 mOverrideDefaultScRoute = mOverrideDefaultFelicaRoute = ROUTE_UNKNOWN;
240     }
241 
overrideDefaultRoute(int defaultRoute)242     public void overrideDefaultRoute(int defaultRoute) {
243         mOverrideDefaultRoute = defaultRoute;
244     }
245 
overrideDefaultIsoDepRoute(int isoDepRoute)246     public void overrideDefaultIsoDepRoute(int isoDepRoute) {
247         mOverrideDefaultIsoDepRoute = isoDepRoute;
248         NfcService.getInstance().setIsoDepProtocolRoute(isoDepRoute);
249     }
250 
overrideDefaultOffHostRoute(int offHostRoute)251     public void overrideDefaultOffHostRoute(int offHostRoute) {
252         mOverrideDefaultOffHostRoute = offHostRoute;
253         mOverrideDefaultFelicaRoute = offHostRoute;
254         NfcService.getInstance().setTechnologyABFRoute(offHostRoute, offHostRoute);
255     }
256 
overrideDefaultScRoute(int scRoute)257     public void overrideDefaultScRoute(int scRoute) {
258         mOverrideDefaultScRoute = scRoute;
259         NfcService.getInstance().setSystemCodeRoute(scRoute);
260     }
261 
recoverOverridedRoutingTable()262     public void recoverOverridedRoutingTable() {
263         NfcService.getInstance().setIsoDepProtocolRoute(mDefaultIsoDepRoute);
264         NfcService.getInstance().setTechnologyABFRoute(mDefaultOffHostRoute, mDefaultFelicaRoute);
265         mOverrideDefaultRoute = mOverrideDefaultIsoDepRoute = mOverrideDefaultOffHostRoute =
266             mOverrideDefaultFelicaRoute = ROUTE_UNKNOWN;
267     }
268 
getOverrideDefaultRoute()269     public int getOverrideDefaultRoute() {
270         return mOverrideDefaultRoute;
271     }
272 
getDefaultRoute()273     public int getDefaultRoute() {
274         return getAlternativeRouteIfSimIsInvalid(mDefaultRoute);
275     }
276 
getOverrideDefaultIsoDepRoute()277     public int getOverrideDefaultIsoDepRoute() {
278         return mOverrideDefaultIsoDepRoute;
279     }
280 
getDefaultIsoDepRoute()281     public int getDefaultIsoDepRoute() {
282         return getAlternativeRouteIfSimIsInvalid(mDefaultIsoDepRoute);
283     }
284 
getOverrideDefaultOffHostRoute()285     public int getOverrideDefaultOffHostRoute() {
286         return mOverrideDefaultOffHostRoute;
287     }
288 
getDefaultOffHostRoute()289     public int getDefaultOffHostRoute() {
290         return getAlternativeRouteIfSimIsInvalid(mDefaultOffHostRoute);
291     }
292 
getDefaultFelicaRoute()293     public int getDefaultFelicaRoute() {
294         return mDefaultFelicaRoute;
295     }
296 
getOverrideDefaultFelicaRoute()297     public int getOverrideDefaultFelicaRoute() {
298         return mOverrideDefaultFelicaRoute;
299     }
300 
getOverrideDefaultScRoute()301     public int getOverrideDefaultScRoute() {
302         return mOverrideDefaultScRoute;
303     }
304 
getDefaultScRoute()305     public int getDefaultScRoute() {
306         return mDefaultScRoute;
307     }
308 
getOffHostRouteUicc()309     public byte[] getOffHostRouteUicc() {
310         return mOffHostRouteUicc;
311     }
312 
getOffHostRouteEse()313     public byte[] getOffHostRouteEse() {
314         return mOffHostRouteEse;
315     }
316 
getAidMatchingSupport()317     public int getAidMatchingSupport() {
318         return mAidMatchingSupport;
319     }
320 
getMepMode()321     public int getMepMode() { return mMepMode;}
322 
isRoutingTableOverrided()323     public boolean isRoutingTableOverrided() {
324         return mOverrideDefaultRoute != ROUTE_UNKNOWN
325             || mOverrideDefaultIsoDepRoute != ROUTE_UNKNOWN
326             || mOverrideDefaultOffHostRoute != ROUTE_UNKNOWN
327             || mOverrideDefaultFelicaRoute != ROUTE_UNKNOWN
328             || mOverrideDefaultScRoute != ROUTE_UNKNOWN;
329     }
330 
createLookUpTable()331     private void createLookUpTable() {
332         mRouteForSecureElement.putIfAbsent(DEVICE_HOST, 0);
333         mSecureElementForRoute.put(0, DEVICE_HOST);
334 
335         mRouteForSecureElement.putIfAbsent("UNKNOWN", ROUTE_UNKNOWN);
336         mSecureElementForRoute.put(ROUTE_UNKNOWN, "UNKNOWN");
337 
338         mRouteForSecureElement.putIfAbsent("default", ROUTE_DEFAULT);
339         mSecureElementForRoute.put(ROUTE_DEFAULT, "default");
340 
341         mRouteForSecureElement.putIfAbsent(SE_NDEF_NFCEE, mNdefNfceeRoute);
342         mSecureElementForRoute.put(mNdefNfceeRoute, SE_NDEF_NFCEE);
343 
344         addOrUpdateTableItems(SE_PREFIX_SIM, mOffHostRouteUicc);
345         addOrUpdateTableItems(SE_PREFIX_ESE, mOffHostRouteEse);
346     }
347 
isRoutingTableOverwrittenOrOverlaid( DeviceConfigFacade deviceConfigFacade, SharedPreferences prefs)348     boolean isRoutingTableOverwrittenOrOverlaid(
349             DeviceConfigFacade deviceConfigFacade, SharedPreferences prefs) {
350         return !TextUtils.isEmpty(deviceConfigFacade.getDefaultRoute())
351                 || !TextUtils.isEmpty(deviceConfigFacade.getDefaultIsoDepRoute())
352                 || !TextUtils.isEmpty(deviceConfigFacade.getDefaultOffHostRoute())
353                 || !TextUtils.isEmpty(deviceConfigFacade.getDefaultScRoute())
354                 || !prefs.getAll().isEmpty();
355     }
356 
readRoutingOptionsFromPrefs( Context context, DeviceConfigFacade deviceConfigFacade)357     public void readRoutingOptionsFromPrefs(
358             Context context, DeviceConfigFacade deviceConfigFacade) {
359         Log.d(TAG, "readRoutingOptionsFromPrefs");
360         if (mPrefs == null) {
361             Log.d(TAG, "readRoutingOptionsFromPrefs: create mPrefs in readRoutingOptions");
362             mContext = context;
363             mPrefs = context.getSharedPreferences(PREF_ROUTING_OPTIONS, Context.MODE_PRIVATE);
364             mIsUiccCapable = context.getPackageManager().hasSystemFeature(
365                     PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC);
366             mIsEseCapable = context.getPackageManager().hasSystemFeature(
367                     PackageManager.FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE);
368         }
369 
370         // If the OEM does not set default routes in the overlay and if no app has overwritten
371         // the routing table using `overwriteRoutingTable`, skip this preference reading.
372         if (!isRoutingTableOverwrittenOrOverlaid(deviceConfigFacade, mPrefs)) {
373             Log.d(TAG, "readRoutingOptionsFromPrefs: Routing table not overwritten or overlaid");
374             return;
375         }
376 
377         // read default route
378         if (!mPrefs.contains(KEY_DEFAULT_ROUTE)) {
379             writeRoutingOption(KEY_DEFAULT_ROUTE, deviceConfigFacade.getDefaultRoute());
380         }
381         mDefaultRoute = getRouteForSecureElement(mPrefs.getString(KEY_DEFAULT_ROUTE, null));
382 
383         // read default iso dep route
384         if (!mPrefs.contains(KEY_DEFAULT_ISO_DEP_ROUTE)) {
385             writeRoutingOption(
386                 KEY_DEFAULT_ISO_DEP_ROUTE, deviceConfigFacade.getDefaultIsoDepRoute());
387         }
388         mDefaultIsoDepRoute =
389             getRouteForSecureElement(mPrefs.getString(KEY_DEFAULT_ISO_DEP_ROUTE, null));
390 
391         // read default offhost route
392         if (!mPrefs.contains(KEY_DEFAULT_OFFHOST_ROUTE)) {
393             writeRoutingOption(
394                 KEY_DEFAULT_OFFHOST_ROUTE, deviceConfigFacade.getDefaultOffHostRoute());
395         }
396         mDefaultOffHostRoute =
397             getRouteForSecureElement(mPrefs.getString(KEY_DEFAULT_OFFHOST_ROUTE, null));
398 
399         // read default system code route
400         if (!mPrefs.contains(KEY_DEFAULT_SC_ROUTE)) {
401             writeRoutingOption(
402                 KEY_DEFAULT_SC_ROUTE, deviceConfigFacade.getDefaultScRoute());
403         }
404         mDefaultScRoute =
405             getRouteForSecureElement(mPrefs.getString(KEY_DEFAULT_SC_ROUTE, null));
406 
407         // read auto change capable
408         if (!mPrefs.contains(KEY_AUTO_CHANGE_CAPABLE)) {
409             writeRoutingOption(KEY_AUTO_CHANGE_CAPABLE, true);
410         }
411         mIsAutoChangeCapable = mPrefs.getBoolean(KEY_AUTO_CHANGE_CAPABLE, true);
412         Log.d(TAG, "readRoutingOptionsFromPrefs: ReadOptions - " + toString());
413     }
414 
setAutoChangeStatus(boolean status)415     public void setAutoChangeStatus(boolean status) {
416         mIsAutoChangeCapable = status;
417     }
418 
isAutoChangeEnabled()419     public boolean isAutoChangeEnabled() {
420         return mIsAutoChangeCapable;
421     }
422 
writeRoutingOption(String key, String name)423     private void writeRoutingOption(String key, String name) {
424         mPrefs.edit().putString(key, name).apply();
425     }
426 
writeRoutingOption(String key, boolean value)427     private void writeRoutingOption(String key, boolean value) {
428         mPrefs.edit().putBoolean(key, value).apply();
429     }
430 
getRouteForSecureElement(String se)431     public int getRouteForSecureElement(String se) {
432         return Optional.ofNullable(mRouteForSecureElement.get(renameSecureElementIfSimType(se)))
433                 .orElseGet(() -> 0x00);
434     }
435 
getSecureElementForRoute(int route)436     public String getSecureElementForRoute(int route) {
437         return Optional.ofNullable(mSecureElementForRoute.get(route)).orElseGet(()->DEVICE_HOST);
438     }
439 
440     // Implement eSIM support
onPreferredSimChanged(int simType)441     public void onPreferredSimChanged(int simType) {
442         mPreferredSimSettings.setType(simType);
443     }
getPreferredSim()444     public String getPreferredSim() {
445         return mPreferredSimSettings.getName();
446     }
renameSecureElementIfSimType(String seName)447     public String renameSecureElementIfSimType(String seName) {
448         return seName.startsWith(SE_PREFIX_SIM) ? mPreferredSimSettings.getName() : seName;
449     }
getAlternativeRouteIfSimIsInvalid(int route)450     private int getAlternativeRouteIfSimIsInvalid(int route) {
451         // TODO - Implement
452         if (getSecureElementForRoute(route).startsWith(SE_PREFIX_SIM)) {
453             if (mPreferredSimSettings.type == TelephonyUtils.SIM_TYPE_UNKNOWN) {
454                 Log.e(TAG, "getAlternativeRouteIfSimIsInvalid: sim is invalid");
455                 return getRouteForSecureElement(mIsEseCapable ? (SE_PREFIX_ESE + 1) : DEVICE_HOST);
456             }
457         }
458         return route;
459     }
460     // Implement eSIM support
addOrUpdateTableItems(String prefix, byte[] routes)461     private void addOrUpdateTableItems(String prefix, byte[] routes) {
462         if (routes != null && routes.length != 0) {
463             for (int index = 1; index <= routes.length; index++) {
464                 int route = routes[index - 1] & 0xFF;
465                 String name = prefix + index;
466                 mRouteForSecureElement.putIfAbsent(name, route);
467                 mSecureElementForRoute.putIfAbsent(route, name);
468             }
469         }
470 
471         for (Map.Entry<String, Integer> entry : mRouteForSecureElement.entrySet()) {
472             Log.d(TAG, "addOrUpdateTableItems: route: " + entry.getKey() + ", nfceeId: "
473                     + Integer.toHexString(entry.getValue()));
474         }
475         for (Map.Entry<Integer, String> entry : mSecureElementForRoute.entrySet()) {
476             Log.d(TAG, "addOrUpdateTableItems: nfceeId: " + Integer.toHexString(entry.getKey())
477                     + ", route: " + entry.getValue());
478         }
479     }
480 }
481