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