1 /* 2 * Copyright 2018 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.internal.telephony; 18 19 import static android.text.format.DateUtils.MINUTE_IN_MILLIS; 20 import static android.text.format.DateUtils.SECOND_IN_MILLIS; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.SharedPreferences; 29 import android.os.AsyncResult; 30 import android.os.Handler; 31 import android.os.Looper; 32 import android.os.Message; 33 import android.os.UserHandle; 34 import android.sysprop.TelephonyProperties; 35 import android.telephony.CellInfo; 36 import android.telephony.ServiceState; 37 import android.telephony.SubscriptionManager; 38 import android.telephony.TelephonyManager; 39 import android.text.TextUtils; 40 import android.util.LocalLog; 41 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.internal.telephony.MccTable.MccMnc; 44 import com.android.internal.telephony.util.TelephonyUtils; 45 import com.android.internal.util.IndentingPrintWriter; 46 import com.android.telephony.Rlog; 47 48 import java.io.FileDescriptor; 49 import java.io.PrintWriter; 50 import java.util.ArrayList; 51 import java.util.HashMap; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Objects; 55 56 /** 57 * The locale tracker keeps tracking the current locale of the phone. 58 */ 59 public class LocaleTracker extends Handler { 60 private static final boolean DBG = true; 61 62 /** Event for getting cell info from the modem */ 63 private static final int EVENT_REQUEST_CELL_INFO = 1; 64 65 /** Event for service state changed */ 66 private static final int EVENT_SERVICE_STATE_CHANGED = 2; 67 68 /** Event for sim state changed */ 69 private static final int EVENT_SIM_STATE_CHANGED = 3; 70 71 /** Event for incoming unsolicited cell info */ 72 private static final int EVENT_UNSOL_CELL_INFO = 4; 73 74 /** Event for incoming cell info */ 75 private static final int EVENT_RESPONSE_CELL_INFO = 5; 76 77 /** Event to fire if the operator from ServiceState is considered truly lost */ 78 private static final int EVENT_OPERATOR_LOST = 6; 79 80 /** Event to override the current locale */ 81 private static final int EVENT_OVERRIDE_LOCALE = 7; 82 83 /** 84 * The broadcast intent action to override the current country for testing purposes 85 * 86 * <p> This broadcast is not effective on user build. 87 * 88 * <p>Example: To override the current country <code> 89 * adb root 90 * adb shell am broadcast -a com.android.internal.telephony.action.COUNTRY_OVERRIDE 91 * --es country us </code> 92 * 93 * <p> To remove the override <code> 94 * adb root 95 * adb shell am broadcast -a com.android.internal.telephony.action.COUNTRY_OVERRIDE 96 * --ez reset true</code> 97 */ 98 private static final String ACTION_COUNTRY_OVERRIDE = 99 "com.android.internal.telephony.action.COUNTRY_OVERRIDE"; 100 101 /** The extra for country override */ 102 private static final String EXTRA_COUNTRY = "country"; 103 104 /** The extra for country override reset */ 105 private static final String EXTRA_RESET = "reset"; 106 107 // Todo: Read this from Settings. 108 /** The minimum delay to get cell info from the modem */ 109 private static final long CELL_INFO_MIN_DELAY_MS = 2 * SECOND_IN_MILLIS; 110 111 // Todo: Read this from Settings. 112 /** The maximum delay to get cell info from the modem */ 113 private static final long CELL_INFO_MAX_DELAY_MS = 10 * MINUTE_IN_MILLIS; 114 115 // Todo: Read this from Settings. 116 /** The delay for periodically getting cell info from the modem */ 117 private static final long CELL_INFO_PERIODIC_POLLING_DELAY_MS = 10 * MINUTE_IN_MILLIS; 118 119 /** 120 * The delay after the last time the device camped on a cell before declaring that the 121 * ServiceState's MCC information can no longer be used (and thus kicking in the CellInfo 122 * based tracking. 123 */ 124 private static final long SERVICE_OPERATOR_LOST_DELAY_MS = 10 * MINUTE_IN_MILLIS; 125 126 /** The maximum fail count to prevent delay time overflow */ 127 private static final int MAX_FAIL_COUNT = 30; 128 129 /** The last known country iso */ 130 private static final String LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY = 131 "last_known_country_iso"; 132 133 private String mTag; 134 135 private final Phone mPhone; 136 137 private final NitzStateMachine mNitzStateMachine; 138 139 /** SIM card state. Must be one of TelephonyManager.SIM_STATE_XXX */ 140 private int mSimState; 141 142 /** Current serving PLMN's MCC/MNC */ 143 @Nullable 144 private String mOperatorNumeric; 145 146 /** Current cell tower information */ 147 @Nullable 148 private List<CellInfo> mCellInfoList; 149 150 /** Count of invalid cell info we've got so far. Will reset once we get a successful one */ 151 private int mFailCellInfoCount; 152 153 /** The ISO-3166 two-letter code of device's current country */ 154 @Nullable 155 private String mCurrentCountryIso; 156 157 /** The country override for testing purposes */ 158 @Nullable 159 private String mCountryOverride; 160 161 /** Current service state. Must be one of ServiceState.STATE_XXX. */ 162 private int mLastServiceState = ServiceState.STATE_POWER_OFF; 163 164 private boolean mIsTracking = false; 165 166 private final LocalLog mLocalLog = new LocalLog(32, false /* useLocalTimestamps */); 167 168 /** Broadcast receiver to get SIM card state changed event */ 169 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 170 @Override 171 public void onReceive(Context context, Intent intent) { 172 if (TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED.equals(intent.getAction())) { 173 int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY, 0); 174 if (phoneId == mPhone.getPhoneId()) { 175 obtainMessage(EVENT_SIM_STATE_CHANGED, 176 intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE, 177 TelephonyManager.SIM_STATE_UNKNOWN), 0).sendToTarget(); 178 } 179 } else if (ACTION_COUNTRY_OVERRIDE.equals(intent.getAction())) { 180 // note: need to set ServiceStateTracker#PROP_FORCE_ROAMING to force roaming. 181 String countryOverride = intent.getStringExtra(EXTRA_COUNTRY); 182 boolean reset = intent.getBooleanExtra(EXTRA_RESET, false); 183 if (reset) countryOverride = null; 184 log("Received country override: " + countryOverride); 185 // countryOverride null to reset the override. 186 obtainMessage(EVENT_OVERRIDE_LOCALE, countryOverride).sendToTarget(); 187 } 188 } 189 }; 190 191 /** 192 * Message handler 193 * 194 * @param msg The message 195 */ 196 @Override handleMessage(Message msg)197 public void handleMessage(Message msg) { 198 switch (msg.what) { 199 case EVENT_REQUEST_CELL_INFO: 200 mPhone.requestCellInfoUpdate(null, obtainMessage(EVENT_RESPONSE_CELL_INFO)); 201 break; 202 203 case EVENT_UNSOL_CELL_INFO: 204 processCellInfo((AsyncResult) msg.obj); 205 // If the unsol happened to be useful, use it; otherwise, pretend it didn't happen. 206 if (mCellInfoList != null && mCellInfoList.size() > 0) requestNextCellInfo(true); 207 break; 208 209 case EVENT_RESPONSE_CELL_INFO: 210 processCellInfo((AsyncResult) msg.obj); 211 // If the cellInfo was non-empty then it's business as usual. Either way, this 212 // cell info was requested by us, so it's our trigger to schedule another one. 213 requestNextCellInfo(mCellInfoList != null && mCellInfoList.size() > 0); 214 break; 215 216 case EVENT_SERVICE_STATE_CHANGED: 217 AsyncResult ar = (AsyncResult) msg.obj; 218 onServiceStateChanged((ServiceState) ar.result); 219 break; 220 221 case EVENT_SIM_STATE_CHANGED: 222 onSimCardStateChanged(msg.arg1); 223 break; 224 225 case EVENT_OPERATOR_LOST: 226 updateOperatorNumericImmediate(""); 227 updateTrackingStatus(); 228 break; 229 230 case EVENT_OVERRIDE_LOCALE: 231 mCountryOverride = (String) msg.obj; 232 updateLocale(); 233 break; 234 235 default: 236 throw new IllegalStateException("Unexpected message arrives. msg = " + msg.what); 237 } 238 } 239 240 /** 241 * Constructor 242 * 243 * @param phone The phone object 244 * @param nitzStateMachine NITZ state machine 245 * @param looper The looper message handler 246 */ LocaleTracker(Phone phone, NitzStateMachine nitzStateMachine, Looper looper)247 public LocaleTracker(Phone phone, NitzStateMachine nitzStateMachine, Looper looper) { 248 super(looper); 249 mPhone = phone; 250 mNitzStateMachine = nitzStateMachine; 251 mSimState = TelephonyManager.SIM_STATE_UNKNOWN; 252 mTag = LocaleTracker.class.getSimpleName() + "-" + mPhone.getPhoneId(); 253 254 final IntentFilter filter = new IntentFilter(); 255 filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED); 256 if (TelephonyUtils.IS_DEBUGGABLE) { 257 filter.addAction(ACTION_COUNTRY_OVERRIDE); 258 } 259 mPhone.getContext().registerReceiver(mBroadcastReceiver, filter); 260 261 mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null); 262 mPhone.registerForCellInfo(this, EVENT_UNSOL_CELL_INFO, null); 263 } 264 265 /** 266 * Get the device's current country. 267 * 268 * @return The device's current country. Empty string if the information is not available. 269 */ 270 @NonNull getCurrentCountry()271 public String getCurrentCountry() { 272 return (mCurrentCountryIso != null) ? mCurrentCountryIso : ""; 273 } 274 275 /** 276 * Get the MCC from cell tower information. 277 * 278 * @return MCC in string format. Null if the information is not available. 279 */ 280 @Nullable getMccFromCellInfo()281 private String getMccFromCellInfo() { 282 String selectedMcc = null; 283 if (mCellInfoList != null) { 284 Map<String, Integer> mccMap = new HashMap<>(); 285 int maxCount = 0; 286 for (CellInfo cellInfo : mCellInfoList) { 287 String mcc = cellInfo.getCellIdentity().getMccString(); 288 if (mcc != null) { 289 int count = 1; 290 if (mccMap.containsKey(mcc)) { 291 count = mccMap.get(mcc) + 1; 292 } 293 mccMap.put(mcc, count); 294 // This is unlikely, but if MCC from cell info looks different, we choose the 295 // MCC that occurs most. 296 if (count > maxCount) { 297 maxCount = count; 298 selectedMcc = mcc; 299 } 300 } 301 } 302 } 303 return selectedMcc; 304 } 305 306 /** 307 * Get the most frequent MCC + MNC combination with the specified MCC using cell tower 308 * information. If no one combination is more frequent than any other an arbitrary MCC + MNC is 309 * returned with the matching MCC. The MNC value returned can be null if it is not provided by 310 * the cell tower information. 311 * 312 * @param mccToMatch the MCC to match 313 * @return a matching {@link MccMnc}. Null if the information is not available. 314 */ 315 @Nullable getMccMncFromCellInfo(@onNull String mccToMatch)316 private MccMnc getMccMncFromCellInfo(@NonNull String mccToMatch) { 317 MccMnc selectedMccMnc = null; 318 if (mCellInfoList != null) { 319 Map<MccMnc, Integer> mccMncMap = new HashMap<>(); 320 int maxCount = 0; 321 for (CellInfo cellInfo : mCellInfoList) { 322 String mcc = cellInfo.getCellIdentity().getMccString(); 323 if (Objects.equals(mcc, mccToMatch)) { 324 String mnc = cellInfo.getCellIdentity().getMncString(); 325 MccMnc mccMnc = new MccMnc(mcc, mnc); 326 int count = 1; 327 if (mccMncMap.containsKey(mccMnc)) { 328 count = mccMncMap.get(mccMnc) + 1; 329 } 330 mccMncMap.put(mccMnc, count); 331 // We keep track of the MCC+MNC combination that occurs most frequently, if 332 // there is one. A null MNC is treated like any other distinct MCC+MNC 333 // combination. 334 if (count > maxCount) { 335 maxCount = count; 336 selectedMccMnc = mccMnc; 337 } 338 } 339 } 340 } 341 return selectedMccMnc; 342 } 343 344 /** 345 * Called when SIM card state changed. Only when we absolutely know the SIM is absent, we get 346 * cell info from the network. Other SIM states like NOT_READY might be just a transitioning 347 * state. 348 * 349 * @param state SIM card state. Must be one of TelephonyManager.SIM_STATE_XXX. 350 */ onSimCardStateChanged(int state)351 private void onSimCardStateChanged(int state) { 352 mSimState = state; 353 updateLocale(); 354 updateTrackingStatus(); 355 } 356 357 /** 358 * Called when service state changed. 359 * 360 * @param serviceState Service state 361 */ onServiceStateChanged(ServiceState serviceState)362 private void onServiceStateChanged(ServiceState serviceState) { 363 mLastServiceState = serviceState.getState(); 364 updateLocale(); 365 updateTrackingStatus(); 366 } 367 368 /** 369 * Update MCC/MNC from network service state. 370 * 371 * @param operatorNumeric MCC/MNC of the operator 372 */ updateOperatorNumeric(String operatorNumeric)373 public void updateOperatorNumeric(String operatorNumeric) { 374 if (TextUtils.isEmpty(operatorNumeric)) { 375 if (!hasMessages(EVENT_OPERATOR_LOST)) { 376 sendMessageDelayed(obtainMessage(EVENT_OPERATOR_LOST), 377 SERVICE_OPERATOR_LOST_DELAY_MS); 378 } 379 } else { 380 removeMessages(EVENT_OPERATOR_LOST); 381 updateOperatorNumericImmediate(operatorNumeric); 382 } 383 } 384 updateOperatorNumericImmediate(String operatorNumeric)385 private void updateOperatorNumericImmediate(String operatorNumeric) { 386 // Check if the operator numeric changes. 387 if (!operatorNumeric.equals(mOperatorNumeric)) { 388 String msg = "Operator numeric changes to \"" + operatorNumeric + "\""; 389 if (DBG) log(msg); 390 mLocalLog.log(msg); 391 mOperatorNumeric = operatorNumeric; 392 updateLocale(); 393 } 394 } 395 processCellInfo(AsyncResult ar)396 private void processCellInfo(AsyncResult ar) { 397 if (ar == null || ar.exception != null) { 398 mCellInfoList = null; 399 return; 400 } 401 List<CellInfo> cellInfoList = (List<CellInfo>) ar.result; 402 String msg = "processCellInfo: cell info=" + cellInfoList; 403 if (DBG) log(msg); 404 mCellInfoList = cellInfoList; 405 updateLocale(); 406 } 407 requestNextCellInfo(boolean succeeded)408 private void requestNextCellInfo(boolean succeeded) { 409 if (!mIsTracking) return; 410 411 removeMessages(EVENT_REQUEST_CELL_INFO); 412 if (succeeded) { 413 resetCellInfoRetry(); 414 // Now we need to get the cell info from the modem periodically 415 // even if we already got the cell info because the user can move. 416 removeMessages(EVENT_UNSOL_CELL_INFO); 417 removeMessages(EVENT_RESPONSE_CELL_INFO); 418 sendMessageDelayed(obtainMessage(EVENT_REQUEST_CELL_INFO), 419 CELL_INFO_PERIODIC_POLLING_DELAY_MS); 420 } else { 421 // If we can't get a valid cell info. Try it again later. 422 long delay = getCellInfoDelayTime(++mFailCellInfoCount); 423 if (DBG) log("Can't get cell info. Try again in " + delay / 1000 + " secs."); 424 sendMessageDelayed(obtainMessage(EVENT_REQUEST_CELL_INFO), delay); 425 } 426 } 427 428 /** 429 * Get the delay time to get cell info from modem. The delay time grows exponentially to prevent 430 * battery draining. 431 * 432 * @param failCount Count of invalid cell info we've got so far. 433 * @return The delay time for next get cell info 434 */ 435 @VisibleForTesting getCellInfoDelayTime(int failCount)436 public static long getCellInfoDelayTime(int failCount) { 437 // Exponentially grow the delay time. Note we limit the fail count to MAX_FAIL_COUNT to 438 // prevent overflow in Math.pow(). 439 long delay = CELL_INFO_MIN_DELAY_MS 440 * (long) Math.pow(2, Math.min(failCount, MAX_FAIL_COUNT) - 1); 441 return Math.min(Math.max(delay, CELL_INFO_MIN_DELAY_MS), CELL_INFO_MAX_DELAY_MS); 442 } 443 444 /** 445 * Stop retrying getting cell info from the modem. It cancels any scheduled cell info retrieving 446 * request. 447 */ resetCellInfoRetry()448 private void resetCellInfoRetry() { 449 mFailCellInfoCount = 0; 450 removeMessages(EVENT_REQUEST_CELL_INFO); 451 } 452 updateTrackingStatus()453 private void updateTrackingStatus() { 454 boolean shouldTrackLocale = 455 (mSimState == TelephonyManager.SIM_STATE_ABSENT 456 || TextUtils.isEmpty(mOperatorNumeric)) 457 && (mLastServiceState == ServiceState.STATE_OUT_OF_SERVICE 458 || mLastServiceState == ServiceState.STATE_EMERGENCY_ONLY); 459 if (shouldTrackLocale) { 460 startTracking(); 461 } else { 462 stopTracking(); 463 } 464 } 465 stopTracking()466 private void stopTracking() { 467 if (!mIsTracking) return; 468 mIsTracking = false; 469 String msg = "Stopping LocaleTracker"; 470 if (DBG) log(msg); 471 mLocalLog.log(msg); 472 mCellInfoList = null; 473 resetCellInfoRetry(); 474 } 475 startTracking()476 private void startTracking() { 477 if (mIsTracking) return; 478 String msg = "Starting LocaleTracker"; 479 mLocalLog.log(msg); 480 if (DBG) log(msg); 481 mIsTracking = true; 482 sendMessage(obtainMessage(EVENT_REQUEST_CELL_INFO)); 483 } 484 485 /** 486 * Update the device's current locale 487 */ updateLocale()488 private synchronized void updateLocale() { 489 // If MCC is available from network service state, use it first. 490 String countryIso = ""; 491 String countryIsoDebugInfo = "empty as default"; 492 493 if (!TextUtils.isEmpty(mOperatorNumeric)) { 494 MccMnc mccMnc = MccMnc.fromOperatorNumeric(mOperatorNumeric); 495 if (mccMnc != null) { 496 countryIso = MccTable.geoCountryCodeForMccMnc(mccMnc); 497 countryIsoDebugInfo = "OperatorNumeric(" + mOperatorNumeric 498 + "): MccTable.geoCountryCodeForMccMnc(\"" + mccMnc + "\")"; 499 } else { 500 loge("updateLocale: Can't get country from operator numeric. mOperatorNumeric = " 501 + mOperatorNumeric); 502 } 503 } 504 505 // If for any reason we can't get country from operator numeric, try to get it from cell 506 // info. 507 if (TextUtils.isEmpty(countryIso)) { 508 // Find the most prevalent MCC from surrounding cell towers. 509 String mcc = getMccFromCellInfo(); 510 if (mcc != null) { 511 countryIso = MccTable.countryCodeForMcc(mcc); 512 countryIsoDebugInfo = "CellInfo: MccTable.countryCodeForMcc(\"" + mcc + "\")"; 513 514 // Some MCC+MNC combinations are known to be used in countries other than those 515 // that the MCC alone would suggest. Do a second pass of nearby cells that match 516 // the most frequently observed MCC to see if this could be one of those cases. 517 MccMnc mccMnc = getMccMncFromCellInfo(mcc); 518 if (mccMnc != null) { 519 countryIso = MccTable.geoCountryCodeForMccMnc(mccMnc); 520 countryIsoDebugInfo = 521 "CellInfo: MccTable.geoCountryCodeForMccMnc(" + mccMnc + ")"; 522 } 523 } 524 } 525 526 if (mCountryOverride != null) { 527 countryIso = mCountryOverride; 528 countryIsoDebugInfo = "mCountryOverride = \"" + mCountryOverride + "\""; 529 } 530 531 if (!mPhone.isRadioOn()) { 532 countryIso = ""; 533 countryIsoDebugInfo = "radio off"; 534 535 // clear cell infos, we don't know where the next network to camp on. 536 mCellInfoList = null; 537 } 538 539 log("updateLocale: countryIso = " + countryIso 540 + ", countryIsoDebugInfo = " + countryIsoDebugInfo); 541 if (!Objects.equals(countryIso, mCurrentCountryIso)) { 542 String msg = "updateLocale: Change the current country to \"" + countryIso + "\"" 543 + ", countryIsoDebugInfo = " + countryIsoDebugInfo 544 + ", mCellInfoList = " + mCellInfoList; 545 log(msg); 546 mLocalLog.log(msg); 547 mCurrentCountryIso = countryIso; 548 549 // Update the last known country ISO 550 if (!TextUtils.isEmpty(mCurrentCountryIso)) { 551 updateLastKnownCountryIso(mCurrentCountryIso); 552 } 553 554 int phoneId = mPhone.getPhoneId(); 555 if (SubscriptionManager.isValidPhoneId(phoneId)) { 556 List<String> newProp = new ArrayList<>( 557 TelephonyProperties.operator_iso_country()); 558 while (newProp.size() <= phoneId) newProp.add(null); 559 newProp.set(phoneId, mCurrentCountryIso); 560 TelephonyProperties.operator_iso_country(newProp); 561 } 562 563 Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED); 564 intent.putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, countryIso); 565 intent.putExtra(TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY, 566 getLastKnownCountryIso()); 567 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId()); 568 mPhone.getContext().sendBroadcastAsUser(intent, UserHandle.ALL); 569 } 570 571 // Pass the geographical country information to the telephony time zone detection code. 572 573 String timeZoneCountryIso = countryIso; 574 String timeZoneCountryIsoDebugInfo = countryIsoDebugInfo; 575 boolean isTestMcc = false; 576 if (!TextUtils.isEmpty(mOperatorNumeric)) { 577 // For a test cell (MCC 001), the NitzStateMachine requires handleCountryDetected("") in 578 // order to pass compliance tests. http://b/142840879 579 if (mOperatorNumeric.startsWith("001")) { 580 isTestMcc = true; 581 timeZoneCountryIso = ""; 582 timeZoneCountryIsoDebugInfo = "Test cell: " + mOperatorNumeric; 583 } 584 } 585 log("updateLocale: timeZoneCountryIso = " + timeZoneCountryIso 586 + ", timeZoneCountryIsoDebugInfo = " + timeZoneCountryIsoDebugInfo); 587 588 if (TextUtils.isEmpty(timeZoneCountryIso) && !isTestMcc) { 589 mNitzStateMachine.handleCountryUnavailable(); 590 } else { 591 mNitzStateMachine.handleCountryDetected(timeZoneCountryIso); 592 } 593 } 594 595 /** Exposed for testing purposes */ isTracking()596 public boolean isTracking() { 597 return mIsTracking; 598 } 599 updateLastKnownCountryIso(String countryIso)600 private void updateLastKnownCountryIso(String countryIso) { 601 if (!TextUtils.isEmpty(countryIso)) { 602 final SharedPreferences prefs = mPhone.getContext().getSharedPreferences( 603 LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, Context.MODE_PRIVATE); 604 final SharedPreferences.Editor editor = prefs.edit(); 605 editor.putString(LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, countryIso); 606 editor.commit(); 607 log("update country iso in sharedPrefs " + countryIso); 608 } 609 } 610 611 /** 612 * Return the last known country ISO before device is not camping on a network 613 * (e.g. Airplane Mode) 614 * 615 * @return The device's last known country ISO. 616 */ 617 @NonNull getLastKnownCountryIso()618 public String getLastKnownCountryIso() { 619 final SharedPreferences prefs = mPhone.getContext().getSharedPreferences( 620 LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, Context.MODE_PRIVATE); 621 return prefs.getString(LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, ""); 622 } 623 log(String msg)624 private void log(String msg) { 625 Rlog.d(mTag, msg); 626 } 627 loge(String msg)628 private void loge(String msg) { 629 Rlog.e(mTag, msg); 630 } 631 632 /** 633 * Print the DeviceStateMonitor into the given stream. 634 * 635 * @param fd The raw file descriptor that the dump is being sent to. 636 * @param pw A PrintWriter to which the dump is to be set. 637 * @param args Additional arguments to the dump request. 638 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)639 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 640 final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); 641 pw.println("LocaleTracker-" + mPhone.getPhoneId() + ":"); 642 ipw.increaseIndent(); 643 ipw.println("mIsTracking = " + mIsTracking); 644 ipw.println("mOperatorNumeric = " + mOperatorNumeric); 645 ipw.println("mSimState = " + mSimState); 646 ipw.println("mCellInfoList = " + mCellInfoList); 647 ipw.println("mCurrentCountryIso = " + mCurrentCountryIso); 648 ipw.println("mFailCellInfoCount = " + mFailCellInfoCount); 649 ipw.println("Local logs:"); 650 ipw.increaseIndent(); 651 mLocalLog.dump(fd, ipw, args); 652 ipw.decreaseIndent(); 653 ipw.decreaseIndent(); 654 ipw.flush(); 655 } 656 657 /** 658 * This getter should only be used for testing purposes in classes that wish to spoof the 659 * country ISO. An example of how this can be done is in ServiceStateTracker#InSameCountry 660 * @return spoofed country iso. 661 */ 662 @VisibleForTesting getCountryOverride()663 public String getCountryOverride() { 664 return mCountryOverride; 665 } 666 } 667