1 /* 2 * Copyright (C) 2023 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.server.thread; 18 19 import static com.android.server.thread.ThreadPersistentSettings.THREAD_COUNTRY_CODE; 20 21 import android.annotation.Nullable; 22 import android.annotation.StringDef; 23 import android.annotation.TargetApi; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.location.Address; 29 import android.location.Geocoder; 30 import android.location.Location; 31 import android.location.LocationManager; 32 import android.net.thread.IOperationReceiver; 33 import android.net.wifi.WifiManager; 34 import android.net.wifi.WifiManager.ActiveCountryCodeChangedCallback; 35 import android.os.Build; 36 import android.sysprop.ThreadNetworkProperties; 37 import android.telephony.SubscriptionInfo; 38 import android.telephony.SubscriptionManager; 39 import android.telephony.TelephonyManager; 40 import android.util.ArrayMap; 41 import android.util.Log; 42 43 import com.android.connectivity.resources.R; 44 import com.android.internal.annotations.VisibleForTesting; 45 import com.android.server.connectivity.ConnectivityResources; 46 47 import java.io.FileDescriptor; 48 import java.io.PrintWriter; 49 import java.lang.annotation.Retention; 50 import java.lang.annotation.RetentionPolicy; 51 import java.time.Instant; 52 import java.util.List; 53 import java.util.Locale; 54 import java.util.Map; 55 import java.util.Objects; 56 57 /** 58 * Provide functions for making changes to Thread Network country code. This Country Code is from 59 * location, WiFi, telephony or OEM configuration. This class sends Country Code to Thread Network 60 * native layer. 61 * 62 * <p>This class is thread-safe. 63 */ 64 @TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 65 public class ThreadNetworkCountryCode { 66 private static final String TAG = "ThreadNetworkCountryCode"; 67 // To be used when there is no country code available. 68 @VisibleForTesting public static final String DEFAULT_COUNTRY_CODE = "WW"; 69 70 // Wait 1 hour between updates. 71 private static final long TIME_BETWEEN_LOCATION_UPDATES_MS = 1000L * 60 * 60 * 1; 72 // Minimum distance before an update is triggered, in meters. We don't need this to be too 73 // exact because all we care about is what country the user is in. 74 private static final float DISTANCE_BETWEEN_LOCALTION_UPDATES_METERS = 5_000.0f; 75 76 /** List of country code sources. */ 77 @Retention(RetentionPolicy.SOURCE) 78 @StringDef( 79 prefix = "COUNTRY_CODE_SOURCE_", 80 value = { 81 COUNTRY_CODE_SOURCE_DEFAULT, 82 COUNTRY_CODE_SOURCE_LOCATION, 83 COUNTRY_CODE_SOURCE_OEM, 84 COUNTRY_CODE_SOURCE_OVERRIDE, 85 COUNTRY_CODE_SOURCE_TELEPHONY, 86 COUNTRY_CODE_SOURCE_TELEPHONY_LAST, 87 COUNTRY_CODE_SOURCE_WIFI, 88 COUNTRY_CODE_SOURCE_SETTINGS, 89 }) 90 private @interface CountryCodeSource {} 91 92 private static final String COUNTRY_CODE_SOURCE_DEFAULT = "Default"; 93 private static final String COUNTRY_CODE_SOURCE_LOCATION = "Location"; 94 private static final String COUNTRY_CODE_SOURCE_OEM = "Oem"; 95 private static final String COUNTRY_CODE_SOURCE_OVERRIDE = "Override"; 96 private static final String COUNTRY_CODE_SOURCE_TELEPHONY = "Telephony"; 97 private static final String COUNTRY_CODE_SOURCE_TELEPHONY_LAST = "TelephonyLast"; 98 private static final String COUNTRY_CODE_SOURCE_WIFI = "Wifi"; 99 private static final String COUNTRY_CODE_SOURCE_SETTINGS = "Settings"; 100 101 private static final CountryCodeInfo DEFAULT_COUNTRY_CODE_INFO = 102 new CountryCodeInfo(DEFAULT_COUNTRY_CODE, COUNTRY_CODE_SOURCE_DEFAULT); 103 104 private final ConnectivityResources mResources; 105 private final Context mContext; 106 private final LocationManager mLocationManager; 107 @Nullable private final Geocoder mGeocoder; 108 private final ThreadNetworkControllerService mThreadNetworkControllerService; 109 private final WifiManager mWifiManager; 110 private final TelephonyManager mTelephonyManager; 111 private final SubscriptionManager mSubscriptionManager; 112 private final Map<Integer, TelephonyCountryCodeSlotInfo> mTelephonyCountryCodeSlotInfoMap = 113 new ArrayMap(); 114 private final ThreadPersistentSettings mPersistentSettings; 115 116 @Nullable private CountryCodeInfo mCurrentCountryCodeInfo; 117 @Nullable private CountryCodeInfo mLocationCountryCodeInfo; 118 @Nullable private CountryCodeInfo mOverrideCountryCodeInfo; 119 @Nullable private CountryCodeInfo mWifiCountryCodeInfo; 120 @Nullable private CountryCodeInfo mTelephonyCountryCodeInfo; 121 @Nullable private CountryCodeInfo mTelephonyLastCountryCodeInfo; 122 @Nullable private CountryCodeInfo mOemCountryCodeInfo; 123 124 /** Container class to store Thread country code information. */ 125 private static final class CountryCodeInfo { 126 private String mCountryCode; 127 @CountryCodeSource private String mSource; 128 private final Instant mUpdatedTimestamp; 129 130 /** 131 * Constructs a new {@code CountryCodeInfo} from the given country code, country code source 132 * and country coode created time. 133 * 134 * @param countryCode a String representation of the country code as defined in ISO 3166. 135 * @param countryCodeSource a String representation of country code source. 136 * @param instant a Instant representation of the time when the country code was created. 137 * @throws IllegalArgumentException if {@code countryCode} contains invalid country code. 138 */ CountryCodeInfo( String countryCode, @CountryCodeSource String countryCodeSource, Instant instant)139 public CountryCodeInfo( 140 String countryCode, @CountryCodeSource String countryCodeSource, Instant instant) { 141 if (!isValidCountryCode(countryCode)) { 142 throw new IllegalArgumentException("Country code is invalid: " + countryCode); 143 } 144 145 mCountryCode = countryCode; 146 mSource = countryCodeSource; 147 mUpdatedTimestamp = instant; 148 } 149 150 /** 151 * Constructs a new {@code CountryCodeInfo} from the given country code, country code 152 * source. The updated timestamp of the country code will be set to the time when {@code 153 * CountryCodeInfo} was constructed. 154 * 155 * @param countryCode a String representation of the country code as defined in ISO 3166. 156 * @param countryCodeSource a String representation of country code source. 157 * @throws IllegalArgumentException if {@code countryCode} contains invalid country code. 158 */ CountryCodeInfo(String countryCode, @CountryCodeSource String countryCodeSource)159 public CountryCodeInfo(String countryCode, @CountryCodeSource String countryCodeSource) { 160 this(countryCode, countryCodeSource, Instant.now()); 161 } 162 getCountryCode()163 public String getCountryCode() { 164 return mCountryCode; 165 } 166 isCountryCodeMatch(CountryCodeInfo countryCodeInfo)167 public boolean isCountryCodeMatch(CountryCodeInfo countryCodeInfo) { 168 if (countryCodeInfo == null) { 169 return false; 170 } 171 172 return Objects.equals(countryCodeInfo.mCountryCode, mCountryCode); 173 } 174 175 @Override toString()176 public String toString() { 177 return "CountryCodeInfo{ mCountryCode: " 178 + mCountryCode 179 + ", mSource: " 180 + mSource 181 + ", mUpdatedTimestamp: " 182 + mUpdatedTimestamp 183 + "}"; 184 } 185 } 186 187 /** Container class to store country code per SIM slot. */ 188 private static final class TelephonyCountryCodeSlotInfo { 189 public int slotIndex; 190 public String countryCode; 191 public String lastKnownCountryCode; 192 public Instant timestamp; 193 194 @Override toString()195 public String toString() { 196 return "TelephonyCountryCodeSlotInfo{ slotIndex: " 197 + slotIndex 198 + ", countryCode: " 199 + countryCode 200 + ", lastKnownCountryCode: " 201 + lastKnownCountryCode 202 + ", timestamp: " 203 + timestamp 204 + "}"; 205 } 206 } 207 isLocationUseForCountryCodeEnabled()208 private boolean isLocationUseForCountryCodeEnabled() { 209 return mResources 210 .get() 211 .getBoolean(R.bool.config_thread_location_use_for_country_code_enabled); 212 } 213 ThreadNetworkCountryCode( LocationManager locationManager, ThreadNetworkControllerService threadNetworkControllerService, @Nullable Geocoder geocoder, ConnectivityResources resources, WifiManager wifiManager, Context context, TelephonyManager telephonyManager, SubscriptionManager subscriptionManager, @Nullable String oemCountryCode, ThreadPersistentSettings persistentSettings)214 public ThreadNetworkCountryCode( 215 LocationManager locationManager, 216 ThreadNetworkControllerService threadNetworkControllerService, 217 @Nullable Geocoder geocoder, 218 ConnectivityResources resources, 219 WifiManager wifiManager, 220 Context context, 221 TelephonyManager telephonyManager, 222 SubscriptionManager subscriptionManager, 223 @Nullable String oemCountryCode, 224 ThreadPersistentSettings persistentSettings) { 225 mLocationManager = locationManager; 226 mThreadNetworkControllerService = threadNetworkControllerService; 227 mGeocoder = geocoder; 228 mResources = resources; 229 mWifiManager = wifiManager; 230 mContext = context; 231 mTelephonyManager = telephonyManager; 232 mSubscriptionManager = subscriptionManager; 233 mPersistentSettings = persistentSettings; 234 235 if (oemCountryCode != null) { 236 mOemCountryCodeInfo = new CountryCodeInfo(oemCountryCode, COUNTRY_CODE_SOURCE_OEM); 237 } 238 239 mCurrentCountryCodeInfo = pickCountryCode(); 240 } 241 newInstance( Context context, ThreadNetworkControllerService controllerService, ThreadPersistentSettings persistentSettings)242 public static ThreadNetworkCountryCode newInstance( 243 Context context, 244 ThreadNetworkControllerService controllerService, 245 ThreadPersistentSettings persistentSettings) { 246 return new ThreadNetworkCountryCode( 247 context.getSystemService(LocationManager.class), 248 controllerService, 249 Geocoder.isPresent() ? new Geocoder(context) : null, 250 new ConnectivityResources(context), 251 context.getSystemService(WifiManager.class), 252 context, 253 context.getSystemService(TelephonyManager.class), 254 context.getSystemService(SubscriptionManager.class), 255 ThreadNetworkProperties.country_code().orElse(null), 256 persistentSettings); 257 } 258 259 /** Sets up this country code module to listen to location country code changes. */ initialize()260 public synchronized void initialize() { 261 registerGeocoderCountryCodeCallback(); 262 registerWifiCountryCodeCallback(); 263 registerTelephonyCountryCodeCallback(); 264 updateTelephonyCountryCodeFromSimCard(); 265 updateCountryCode(false /* forceUpdate */); 266 } 267 registerGeocoderCountryCodeCallback()268 private synchronized void registerGeocoderCountryCodeCallback() { 269 if ((mGeocoder != null) && isLocationUseForCountryCodeEnabled()) { 270 mLocationManager.requestLocationUpdates( 271 LocationManager.PASSIVE_PROVIDER, 272 TIME_BETWEEN_LOCATION_UPDATES_MS, 273 DISTANCE_BETWEEN_LOCALTION_UPDATES_METERS, 274 location -> setCountryCodeFromGeocodingLocation(location)); 275 } 276 } 277 geocodeListener(List<Address> addresses)278 private synchronized void geocodeListener(List<Address> addresses) { 279 if (addresses != null && !addresses.isEmpty()) { 280 String countryCode = addresses.get(0).getCountryCode(); 281 282 if (isValidCountryCode(countryCode)) { 283 Log.d(TAG, "Set location country code to: " + countryCode); 284 mLocationCountryCodeInfo = 285 new CountryCodeInfo(countryCode, COUNTRY_CODE_SOURCE_LOCATION); 286 } else { 287 Log.d(TAG, "Received invalid location country code"); 288 mLocationCountryCodeInfo = null; 289 } 290 291 updateCountryCode(false /* forceUpdate */); 292 } 293 } 294 setCountryCodeFromGeocodingLocation(@ullable Location location)295 private synchronized void setCountryCodeFromGeocodingLocation(@Nullable Location location) { 296 if ((location == null) || (mGeocoder == null)) return; 297 298 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) { 299 Log.wtf( 300 TAG, 301 "Unexpected call to set country code from the Geocoding location, " 302 + "Thread code never runs under T or lower."); 303 return; 304 } 305 306 mGeocoder.getFromLocation( 307 location.getLatitude(), 308 location.getLongitude(), 309 1 /* maxResults */, 310 this::geocodeListener); 311 } 312 registerWifiCountryCodeCallback()313 private synchronized void registerWifiCountryCodeCallback() { 314 if (mWifiManager != null) { 315 mWifiManager.registerActiveCountryCodeChangedCallback( 316 r -> r.run(), new WifiCountryCodeCallback()); 317 } 318 } 319 320 private class WifiCountryCodeCallback implements ActiveCountryCodeChangedCallback { 321 @Override onActiveCountryCodeChanged(String countryCode)322 public void onActiveCountryCodeChanged(String countryCode) { 323 Log.d(TAG, "Wifi country code is changed to " + countryCode); 324 synchronized ("ThreadNetworkCountryCode.this") { 325 if (isValidCountryCode(countryCode)) { 326 mWifiCountryCodeInfo = 327 new CountryCodeInfo(countryCode, COUNTRY_CODE_SOURCE_WIFI); 328 } else { 329 Log.w(TAG, "WiFi country code " + countryCode + " is invalid"); 330 mWifiCountryCodeInfo = null; 331 } 332 333 updateCountryCode(false /* forceUpdate */); 334 } 335 } 336 337 @Override onCountryCodeInactive()338 public void onCountryCodeInactive() { 339 Log.d(TAG, "Wifi country code is inactived"); 340 synchronized ("ThreadNetworkCountryCode.this") { 341 mWifiCountryCodeInfo = null; 342 updateCountryCode(false /* forceUpdate */); 343 } 344 } 345 } 346 registerTelephonyCountryCodeCallback()347 private synchronized void registerTelephonyCountryCodeCallback() { 348 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { 349 Log.wtf( 350 TAG, 351 "Unexpected call to register the telephony country code changed callback, " 352 + "Thread code never runs under T or lower."); 353 return; 354 } 355 356 BroadcastReceiver broadcastReceiver = 357 new BroadcastReceiver() { 358 @Override 359 public void onReceive(Context context, Intent intent) { 360 int slotIndex = 361 intent.getIntExtra( 362 SubscriptionManager.EXTRA_SLOT_INDEX, 363 SubscriptionManager.INVALID_SIM_SLOT_INDEX); 364 String lastKnownCountryCode = null; 365 String countryCode = 366 intent.getStringExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY); 367 368 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 369 lastKnownCountryCode = 370 intent.getStringExtra( 371 TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY); 372 } 373 374 setTelephonyCountryCodeAndLastKnownCountryCode( 375 slotIndex, countryCode, lastKnownCountryCode); 376 } 377 }; 378 379 mContext.registerReceiver( 380 broadcastReceiver, 381 new IntentFilter(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED), 382 Context.RECEIVER_EXPORTED); 383 } 384 updateTelephonyCountryCodeFromSimCard()385 private synchronized void updateTelephonyCountryCodeFromSimCard() { 386 List<SubscriptionInfo> subscriptionInfoList = 387 mSubscriptionManager.getActiveSubscriptionInfoList(); 388 389 if (subscriptionInfoList == null) { 390 Log.d(TAG, "No SIM card is found"); 391 return; 392 } 393 394 for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) { 395 String countryCode; 396 int slotIndex; 397 398 slotIndex = subscriptionInfo.getSimSlotIndex(); 399 try { 400 countryCode = mTelephonyManager.getNetworkCountryIso(slotIndex); 401 } catch (IllegalArgumentException e) { 402 Log.e(TAG, "Failed to get country code for slot index:" + slotIndex, e); 403 continue; 404 } 405 406 Log.d(TAG, "Telephony slot " + slotIndex + " country code is " + countryCode); 407 setTelephonyCountryCodeAndLastKnownCountryCode( 408 slotIndex, countryCode, null /* lastKnownCountryCode */); 409 } 410 } 411 setTelephonyCountryCodeAndLastKnownCountryCode( int slotIndex, String countryCode, String lastKnownCountryCode)412 private synchronized void setTelephonyCountryCodeAndLastKnownCountryCode( 413 int slotIndex, String countryCode, String lastKnownCountryCode) { 414 Log.d( 415 TAG, 416 "Set telephony country code to: " 417 + countryCode 418 + ", last country code to: " 419 + lastKnownCountryCode 420 + " for slotIndex: " 421 + slotIndex); 422 423 TelephonyCountryCodeSlotInfo telephonyCountryCodeInfoSlot = 424 mTelephonyCountryCodeSlotInfoMap.computeIfAbsent( 425 slotIndex, k -> new TelephonyCountryCodeSlotInfo()); 426 telephonyCountryCodeInfoSlot.slotIndex = slotIndex; 427 telephonyCountryCodeInfoSlot.timestamp = Instant.now(); 428 telephonyCountryCodeInfoSlot.countryCode = countryCode; 429 telephonyCountryCodeInfoSlot.lastKnownCountryCode = lastKnownCountryCode; 430 431 mTelephonyCountryCodeInfo = null; 432 mTelephonyLastCountryCodeInfo = null; 433 434 for (TelephonyCountryCodeSlotInfo slotInfo : mTelephonyCountryCodeSlotInfoMap.values()) { 435 if ((mTelephonyCountryCodeInfo == null) && isValidCountryCode(slotInfo.countryCode)) { 436 mTelephonyCountryCodeInfo = 437 new CountryCodeInfo( 438 slotInfo.countryCode, 439 COUNTRY_CODE_SOURCE_TELEPHONY, 440 slotInfo.timestamp); 441 } 442 443 if ((mTelephonyLastCountryCodeInfo == null) 444 && isValidCountryCode(slotInfo.lastKnownCountryCode)) { 445 mTelephonyLastCountryCodeInfo = 446 new CountryCodeInfo( 447 slotInfo.lastKnownCountryCode, 448 COUNTRY_CODE_SOURCE_TELEPHONY_LAST, 449 slotInfo.timestamp); 450 } 451 } 452 453 updateCountryCode(false /* forceUpdate */); 454 } 455 456 /** 457 * Priority order of country code sources (we stop at the first known country code source): 458 * 459 * <ul> 460 * <li>1. Override country code - Country code forced via shell command (local/automated 461 * testing) 462 * <li>2. Telephony country code - Current country code retrieved via cellular. If there are 463 * multiple SIM's, the country code chosen is non-deterministic if they return different 464 * codes. The first valid country code with the lowest slot number will be used. 465 * <li>3. Wifi country code - Current country code retrieved via wifi (via 80211.ad). 466 * <li>4. Last known telephony country code - Last known country code retrieved via cellular. 467 * If there are multiple SIM's, the country code chosen is non-deterministic if they 468 * return different codes. The first valid last known country code with the lowest slot 469 * number will be used. 470 * <li>5. Location country code - Country code retrieved from LocationManager passive location 471 * provider. 472 * <li>6. OEM country code - Country code retrieved from the system property 473 * `threadnetwork.country_code`. 474 * <li>7. Default country code `WW`. 475 * </ul> 476 * 477 * @return the selected country code information. 478 */ pickCountryCode()479 private CountryCodeInfo pickCountryCode() { 480 if (mOverrideCountryCodeInfo != null) { 481 return mOverrideCountryCodeInfo; 482 } 483 484 if (mTelephonyCountryCodeInfo != null) { 485 return mTelephonyCountryCodeInfo; 486 } 487 488 if (mWifiCountryCodeInfo != null) { 489 return mWifiCountryCodeInfo; 490 } 491 492 if (mTelephonyLastCountryCodeInfo != null) { 493 return mTelephonyLastCountryCodeInfo; 494 } 495 496 if (mLocationCountryCodeInfo != null) { 497 return mLocationCountryCodeInfo; 498 } 499 500 String settingsCountryCode = mPersistentSettings.get(THREAD_COUNTRY_CODE); 501 if (settingsCountryCode != null) { 502 return new CountryCodeInfo(settingsCountryCode, COUNTRY_CODE_SOURCE_SETTINGS); 503 } 504 505 if (mOemCountryCodeInfo != null) { 506 return mOemCountryCodeInfo; 507 } 508 509 return DEFAULT_COUNTRY_CODE_INFO; 510 } 511 newOperationReceiver(CountryCodeInfo countryCodeInfo)512 private IOperationReceiver newOperationReceiver(CountryCodeInfo countryCodeInfo) { 513 return new IOperationReceiver.Stub() { 514 @Override 515 public void onSuccess() { 516 synchronized ("ThreadNetworkCountryCode.this") { 517 mCurrentCountryCodeInfo = countryCodeInfo; 518 mPersistentSettings.put( 519 THREAD_COUNTRY_CODE.key, countryCodeInfo.getCountryCode()); 520 } 521 } 522 523 @Override 524 public void onError(int otError, String message) { 525 Log.e( 526 TAG, 527 "Error " 528 + otError 529 + ": " 530 + message 531 + ". Failed to set country code " 532 + countryCodeInfo); 533 } 534 }; 535 } 536 537 /** 538 * Updates country code to the Thread native layer. 539 * 540 * @param forceUpdate Force update the country code even if it was the same as previously cached 541 * value. 542 */ 543 @VisibleForTesting 544 public synchronized void updateCountryCode(boolean forceUpdate) { 545 CountryCodeInfo countryCodeInfo = pickCountryCode(); 546 547 if (!forceUpdate && countryCodeInfo.isCountryCodeMatch(mCurrentCountryCodeInfo)) { 548 Log.i(TAG, "Ignoring already set country code " + countryCodeInfo.getCountryCode()); 549 return; 550 } 551 552 Log.i(TAG, "Set country code: " + countryCodeInfo); 553 mThreadNetworkControllerService.setCountryCode( 554 countryCodeInfo.getCountryCode().toUpperCase(Locale.ROOT), 555 newOperationReceiver(countryCodeInfo)); 556 } 557 558 /** Returns the current country code. */ 559 public synchronized String getCountryCode() { 560 return mCurrentCountryCodeInfo.getCountryCode(); 561 } 562 563 /** 564 * Returns {@code true} if {@code countryCode} is a valid country code. 565 * 566 * <p>A country code is valid if it consists of 2 alphabets. 567 */ 568 public static boolean isValidCountryCode(String countryCode) { 569 return countryCode != null 570 && countryCode.length() == 2 571 && countryCode.chars().allMatch(Character::isLetter); 572 } 573 574 /** 575 * Overrides any existing country code. 576 * 577 * @param countryCode A 2-Character alphabetical country code (as defined in ISO 3166). 578 * @throws IllegalArgumentException if {@code countryCode} is an invalid country code. 579 */ 580 public synchronized void setOverrideCountryCode(String countryCode) { 581 if (!isValidCountryCode(countryCode)) { 582 throw new IllegalArgumentException("The override country code is invalid"); 583 } 584 585 mOverrideCountryCodeInfo = new CountryCodeInfo(countryCode, COUNTRY_CODE_SOURCE_OVERRIDE); 586 updateCountryCode(true /* forceUpdate */); 587 } 588 589 /** Clears the country code previously set through {@link #setOverrideCountryCode} method. */ 590 public synchronized void clearOverrideCountryCode() { 591 mOverrideCountryCodeInfo = null; 592 updateCountryCode(true /* forceUpdate */); 593 } 594 595 /** Dumps the current state of this ThreadNetworkCountryCode object. */ 596 public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 597 pw.println("---- Dump of ThreadNetworkCountryCode begin ----"); 598 pw.println("mOverrideCountryCodeInfo : " + mOverrideCountryCodeInfo); 599 pw.println("mTelephonyCountryCodeSlotInfoMap: " + mTelephonyCountryCodeSlotInfoMap); 600 pw.println("mTelephonyCountryCodeInfo : " + mTelephonyCountryCodeInfo); 601 pw.println("mWifiCountryCodeInfo : " + mWifiCountryCodeInfo); 602 pw.println("mTelephonyLastCountryCodeInfo : " + mTelephonyLastCountryCodeInfo); 603 pw.println("mLocationCountryCodeInfo : " + mLocationCountryCodeInfo); 604 pw.println("mOemCountryCodeInfo : " + mOemCountryCodeInfo); 605 pw.println("mCurrentCountryCodeInfo : " + mCurrentCountryCodeInfo); 606 pw.println("---- Dump of ThreadNetworkCountryCode end ------"); 607 } 608 } 609