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(50); 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 String countryOverride = intent.getStringExtra(EXTRA_COUNTRY); 180 boolean reset = intent.getBooleanExtra(EXTRA_RESET, false); 181 if (reset) countryOverride = null; 182 log("Received country override: " + countryOverride); 183 // countryOverride null to reset the override. 184 obtainMessage(EVENT_OVERRIDE_LOCALE, countryOverride).sendToTarget(); 185 } 186 } 187 }; 188 189 /** 190 * Message handler 191 * 192 * @param msg The message 193 */ 194 @Override handleMessage(Message msg)195 public void handleMessage(Message msg) { 196 switch (msg.what) { 197 case EVENT_REQUEST_CELL_INFO: 198 mPhone.requestCellInfoUpdate(null, obtainMessage(EVENT_RESPONSE_CELL_INFO)); 199 break; 200 201 case EVENT_UNSOL_CELL_INFO: 202 processCellInfo((AsyncResult) msg.obj); 203 // If the unsol happened to be useful, use it; otherwise, pretend it didn't happen. 204 if (mCellInfoList != null && mCellInfoList.size() > 0) requestNextCellInfo(true); 205 break; 206 207 case EVENT_RESPONSE_CELL_INFO: 208 processCellInfo((AsyncResult) msg.obj); 209 // If the cellInfo was non-empty then it's business as usual. Either way, this 210 // cell info was requested by us, so it's our trigger to schedule another one. 211 requestNextCellInfo(mCellInfoList != null && mCellInfoList.size() > 0); 212 break; 213 214 case EVENT_SERVICE_STATE_CHANGED: 215 AsyncResult ar = (AsyncResult) msg.obj; 216 onServiceStateChanged((ServiceState) ar.result); 217 break; 218 219 case EVENT_SIM_STATE_CHANGED: 220 onSimCardStateChanged(msg.arg1); 221 break; 222 223 case EVENT_OPERATOR_LOST: 224 updateOperatorNumericImmediate(""); 225 updateTrackingStatus(); 226 break; 227 228 case EVENT_OVERRIDE_LOCALE: 229 mCountryOverride = (String) msg.obj; 230 updateLocale(); 231 break; 232 233 default: 234 throw new IllegalStateException("Unexpected message arrives. msg = " + msg.what); 235 } 236 } 237 238 /** 239 * Constructor 240 * 241 * @param phone The phone object 242 * @param nitzStateMachine NITZ state machine 243 * @param looper The looper message handler 244 */ LocaleTracker(Phone phone, NitzStateMachine nitzStateMachine, Looper looper)245 public LocaleTracker(Phone phone, NitzStateMachine nitzStateMachine, Looper looper) { 246 super(looper); 247 mPhone = phone; 248 mNitzStateMachine = nitzStateMachine; 249 mSimState = TelephonyManager.SIM_STATE_UNKNOWN; 250 mTag = LocaleTracker.class.getSimpleName() + "-" + mPhone.getPhoneId(); 251 252 final IntentFilter filter = new IntentFilter(); 253 filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED); 254 if (TelephonyUtils.IS_DEBUGGABLE) { 255 filter.addAction(ACTION_COUNTRY_OVERRIDE); 256 } 257 mPhone.getContext().registerReceiver(mBroadcastReceiver, filter); 258 259 mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null); 260 mPhone.registerForCellInfo(this, EVENT_UNSOL_CELL_INFO, null); 261 } 262 263 /** 264 * Get the device's current country. 265 * 266 * @return The device's current country. Empty string if the information is not available. 267 */ 268 @NonNull getCurrentCountry()269 public String getCurrentCountry() { 270 return (mCurrentCountryIso != null) ? mCurrentCountryIso : ""; 271 } 272 273 /** 274 * Get the MCC from cell tower information. 275 * 276 * @return MCC in string format. Null if the information is not available. 277 */ 278 @Nullable getMccFromCellInfo()279 private String getMccFromCellInfo() { 280 String selectedMcc = null; 281 if (mCellInfoList != null) { 282 Map<String, Integer> mccMap = new HashMap<>(); 283 int maxCount = 0; 284 for (CellInfo cellInfo : mCellInfoList) { 285 String mcc = cellInfo.getCellIdentity().getMccString(); 286 if (mcc != null) { 287 int count = 1; 288 if (mccMap.containsKey(mcc)) { 289 count = mccMap.get(mcc) + 1; 290 } 291 mccMap.put(mcc, count); 292 // This is unlikely, but if MCC from cell info looks different, we choose the 293 // MCC that occurs most. 294 if (count > maxCount) { 295 maxCount = count; 296 selectedMcc = mcc; 297 } 298 } 299 } 300 } 301 return selectedMcc; 302 } 303 304 /** 305 * Get the most frequent MCC + MNC combination with the specified MCC using cell tower 306 * information. If no one combination is more frequent than any other an arbitrary MCC + MNC is 307 * returned with the matching MCC. The MNC value returned can be null if it is not provided by 308 * the cell tower information. 309 * 310 * @param mccToMatch the MCC to match 311 * @return a matching {@link MccMnc}. Null if the information is not available. 312 */ 313 @Nullable getMccMncFromCellInfo(@onNull String mccToMatch)314 private MccMnc getMccMncFromCellInfo(@NonNull String mccToMatch) { 315 MccMnc selectedMccMnc = null; 316 if (mCellInfoList != null) { 317 Map<MccMnc, Integer> mccMncMap = new HashMap<>(); 318 int maxCount = 0; 319 for (CellInfo cellInfo : mCellInfoList) { 320 String mcc = cellInfo.getCellIdentity().getMccString(); 321 if (Objects.equals(mcc, mccToMatch)) { 322 String mnc = cellInfo.getCellIdentity().getMncString(); 323 MccMnc mccMnc = new MccMnc(mcc, mnc); 324 int count = 1; 325 if (mccMncMap.containsKey(mccMnc)) { 326 count = mccMncMap.get(mccMnc) + 1; 327 } 328 mccMncMap.put(mccMnc, count); 329 // We keep track of the MCC+MNC combination that occurs most frequently, if 330 // there is one. A null MNC is treated like any other distinct MCC+MNC 331 // combination. 332 if (count > maxCount) { 333 maxCount = count; 334 selectedMccMnc = mccMnc; 335 } 336 } 337 } 338 } 339 return selectedMccMnc; 340 } 341 342 /** 343 * Called when SIM card state changed. Only when we absolutely know the SIM is absent, we get 344 * cell info from the network. Other SIM states like NOT_READY might be just a transitioning 345 * state. 346 * 347 * @param state SIM card state. Must be one of TelephonyManager.SIM_STATE_XXX. 348 */ onSimCardStateChanged(int state)349 private void onSimCardStateChanged(int state) { 350 mSimState = state; 351 updateLocale(); 352 updateTrackingStatus(); 353 } 354 355 /** 356 * Called when service state changed. 357 * 358 * @param serviceState Service state 359 */ onServiceStateChanged(ServiceState serviceState)360 private void onServiceStateChanged(ServiceState serviceState) { 361 mLastServiceState = serviceState.getState(); 362 updateLocale(); 363 updateTrackingStatus(); 364 } 365 366 /** 367 * Update MCC/MNC from network service state. 368 * 369 * @param operatorNumeric MCC/MNC of the operator 370 */ updateOperatorNumeric(String operatorNumeric)371 public void updateOperatorNumeric(String operatorNumeric) { 372 if (TextUtils.isEmpty(operatorNumeric)) { 373 sendMessageDelayed(obtainMessage(EVENT_OPERATOR_LOST), SERVICE_OPERATOR_LOST_DELAY_MS); 374 } else { 375 removeMessages(EVENT_OPERATOR_LOST); 376 updateOperatorNumericImmediate(operatorNumeric); 377 } 378 } 379 updateOperatorNumericImmediate(String operatorNumeric)380 private void updateOperatorNumericImmediate(String operatorNumeric) { 381 // Check if the operator numeric changes. 382 if (!operatorNumeric.equals(mOperatorNumeric)) { 383 String msg = "Operator numeric changes to \"" + operatorNumeric + "\""; 384 if (DBG) log(msg); 385 mLocalLog.log(msg); 386 mOperatorNumeric = operatorNumeric; 387 updateLocale(); 388 } 389 } 390 processCellInfo(AsyncResult ar)391 private void processCellInfo(AsyncResult ar) { 392 if (ar == null || ar.exception != null) { 393 mCellInfoList = null; 394 return; 395 } 396 List<CellInfo> cellInfoList = (List<CellInfo>) ar.result; 397 String msg = "processCellInfo: cell info=" + cellInfoList; 398 if (DBG) log(msg); 399 mCellInfoList = cellInfoList; 400 updateLocale(); 401 } 402 requestNextCellInfo(boolean succeeded)403 private void requestNextCellInfo(boolean succeeded) { 404 if (!mIsTracking) return; 405 406 removeMessages(EVENT_REQUEST_CELL_INFO); 407 if (succeeded) { 408 resetCellInfoRetry(); 409 // Now we need to get the cell info from the modem periodically 410 // even if we already got the cell info because the user can move. 411 removeMessages(EVENT_UNSOL_CELL_INFO); 412 removeMessages(EVENT_RESPONSE_CELL_INFO); 413 sendMessageDelayed(obtainMessage(EVENT_REQUEST_CELL_INFO), 414 CELL_INFO_PERIODIC_POLLING_DELAY_MS); 415 } else { 416 // If we can't get a valid cell info. Try it again later. 417 long delay = getCellInfoDelayTime(++mFailCellInfoCount); 418 if (DBG) log("Can't get cell info. Try again in " + delay / 1000 + " secs."); 419 sendMessageDelayed(obtainMessage(EVENT_REQUEST_CELL_INFO), delay); 420 } 421 } 422 423 /** 424 * Get the delay time to get cell info from modem. The delay time grows exponentially to prevent 425 * battery draining. 426 * 427 * @param failCount Count of invalid cell info we've got so far. 428 * @return The delay time for next get cell info 429 */ 430 @VisibleForTesting getCellInfoDelayTime(int failCount)431 public static long getCellInfoDelayTime(int failCount) { 432 // Exponentially grow the delay time. Note we limit the fail count to MAX_FAIL_COUNT to 433 // prevent overflow in Math.pow(). 434 long delay = CELL_INFO_MIN_DELAY_MS 435 * (long) Math.pow(2, Math.min(failCount, MAX_FAIL_COUNT) - 1); 436 return Math.min(Math.max(delay, CELL_INFO_MIN_DELAY_MS), CELL_INFO_MAX_DELAY_MS); 437 } 438 439 /** 440 * Stop retrying getting cell info from the modem. It cancels any scheduled cell info retrieving 441 * request. 442 */ resetCellInfoRetry()443 private void resetCellInfoRetry() { 444 mFailCellInfoCount = 0; 445 removeMessages(EVENT_REQUEST_CELL_INFO); 446 } 447 updateTrackingStatus()448 private void updateTrackingStatus() { 449 boolean shouldTrackLocale = 450 (mSimState == TelephonyManager.SIM_STATE_ABSENT 451 || TextUtils.isEmpty(mOperatorNumeric)) 452 && (mLastServiceState == ServiceState.STATE_OUT_OF_SERVICE 453 || mLastServiceState == ServiceState.STATE_EMERGENCY_ONLY); 454 if (shouldTrackLocale) { 455 startTracking(); 456 } else { 457 stopTracking(); 458 } 459 } 460 stopTracking()461 private void stopTracking() { 462 if (!mIsTracking) return; 463 mIsTracking = false; 464 String msg = "Stopping LocaleTracker"; 465 if (DBG) log(msg); 466 mLocalLog.log(msg); 467 mCellInfoList = null; 468 resetCellInfoRetry(); 469 } 470 startTracking()471 private void startTracking() { 472 if (mIsTracking) return; 473 String msg = "Starting LocaleTracker"; 474 mLocalLog.log(msg); 475 if (DBG) log(msg); 476 mIsTracking = true; 477 sendMessage(obtainMessage(EVENT_REQUEST_CELL_INFO)); 478 } 479 480 /** 481 * Update the device's current locale 482 */ updateLocale()483 private synchronized void updateLocale() { 484 // If MCC is available from network service state, use it first. 485 String countryIso = ""; 486 String countryIsoDebugInfo = "empty as default"; 487 488 // For time zone detection we want the best geographical match we can get, which may differ 489 // from the countryIso. 490 String timeZoneCountryIso = null; 491 String timeZoneCountryIsoDebugInfo = null; 492 493 if (!TextUtils.isEmpty(mOperatorNumeric)) { 494 MccMnc mccMnc = MccMnc.fromOperatorNumeric(mOperatorNumeric); 495 if (mccMnc != null) { 496 countryIso = MccTable.countryCodeForMcc(mccMnc.mcc); 497 countryIsoDebugInfo = "OperatorNumeric(" + mOperatorNumeric 498 + "): MccTable.countryCodeForMcc(\"" + mccMnc.mcc + "\")"; 499 timeZoneCountryIso = MccTable.geoCountryCodeForMccMnc(mccMnc); 500 timeZoneCountryIsoDebugInfo = 501 "OperatorNumeric: MccTable.geoCountryCodeForMccMnc(" + mccMnc + ")"; 502 } else { 503 loge("updateLocale: Can't get country from operator numeric. mOperatorNumeric = " 504 + mOperatorNumeric); 505 } 506 } 507 508 // If for any reason we can't get country from operator numeric, try to get it from cell 509 // info. 510 if (TextUtils.isEmpty(countryIso)) { 511 String mcc = getMccFromCellInfo(); 512 if (mcc != null) { 513 countryIso = MccTable.countryCodeForMcc(mcc); 514 countryIsoDebugInfo = "CellInfo: MccTable.countryCodeForMcc(\"" + mcc + "\")"; 515 516 MccMnc mccMnc = getMccMncFromCellInfo(mcc); 517 if (mccMnc != null) { 518 timeZoneCountryIso = MccTable.geoCountryCodeForMccMnc(mccMnc); 519 timeZoneCountryIsoDebugInfo = 520 "CellInfo: MccTable.geoCountryCodeForMccMnc(" + mccMnc + ")"; 521 } 522 } 523 } 524 525 if (mCountryOverride != null) { 526 countryIso = mCountryOverride; 527 countryIsoDebugInfo = "mCountryOverride = \"" + mCountryOverride + "\""; 528 timeZoneCountryIso = countryIso; 529 timeZoneCountryIsoDebugInfo = countryIsoDebugInfo; 530 } 531 532 if (!mPhone.isRadioOn()) { 533 countryIso = ""; 534 countryIsoDebugInfo = "radio off"; 535 } 536 537 log("updateLocale: countryIso = " + countryIso 538 + ", countryIsoDebugInfo = " + countryIsoDebugInfo); 539 if (!Objects.equals(countryIso, mCurrentCountryIso)) { 540 String msg = "updateLocale: Change the current country to \"" + countryIso + "\"" 541 + ", countryIsoDebugInfo = " + countryIsoDebugInfo 542 + ", mCellInfoList = " + mCellInfoList; 543 log(msg); 544 mLocalLog.log(msg); 545 mCurrentCountryIso = countryIso; 546 547 // Update the last known country ISO 548 if (!TextUtils.isEmpty(mCurrentCountryIso)) { 549 updateLastKnownCountryIso(mCurrentCountryIso); 550 } 551 552 int phoneId = mPhone.getPhoneId(); 553 if (SubscriptionManager.isValidPhoneId(phoneId)) { 554 List<String> newProp = new ArrayList<>( 555 TelephonyProperties.operator_iso_country()); 556 while (newProp.size() <= phoneId) newProp.add(null); 557 newProp.set(phoneId, mCurrentCountryIso); 558 TelephonyProperties.operator_iso_country(newProp); 559 } 560 561 Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED); 562 intent.putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, countryIso); 563 intent.putExtra(TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY, 564 getLastKnownCountryIso()); 565 SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId()); 566 mPhone.getContext().sendBroadcast(intent); 567 } 568 569 // Pass the geographical country information to the telephony time zone detection code. 570 571 boolean isTestMcc = false; 572 if (!TextUtils.isEmpty(mOperatorNumeric)) { 573 // For a test cell (MCC 001), the NitzStateMachine requires handleCountryDetected("") in 574 // order to pass compliance tests. http://b/142840879 575 if (mOperatorNumeric.startsWith("001")) { 576 isTestMcc = true; 577 timeZoneCountryIso = ""; 578 timeZoneCountryIsoDebugInfo = "Test cell: " + mOperatorNumeric; 579 } 580 } 581 if (timeZoneCountryIso == null) { 582 // After this timeZoneCountryIso may still be null. 583 timeZoneCountryIso = countryIso; 584 timeZoneCountryIsoDebugInfo = "Defaulted: " + countryIsoDebugInfo; 585 } 586 log("updateLocale: timeZoneCountryIso = " + timeZoneCountryIso 587 + ", timeZoneCountryIsoDebugInfo = " + timeZoneCountryIsoDebugInfo); 588 589 if (TextUtils.isEmpty(timeZoneCountryIso) && !isTestMcc) { 590 mNitzStateMachine.handleCountryUnavailable(); 591 } else { 592 mNitzStateMachine.handleCountryDetected(timeZoneCountryIso); 593 } 594 } 595 596 /** Exposed for testing purposes */ isTracking()597 public boolean isTracking() { 598 return mIsTracking; 599 } 600 updateLastKnownCountryIso(String countryIso)601 private void updateLastKnownCountryIso(String countryIso) { 602 if (!TextUtils.isEmpty(countryIso)) { 603 final SharedPreferences prefs = mPhone.getContext().getSharedPreferences( 604 LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, Context.MODE_PRIVATE); 605 final SharedPreferences.Editor editor = prefs.edit(); 606 editor.putString(LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, countryIso); 607 editor.commit(); 608 log("update country iso in sharedPrefs " + countryIso); 609 } 610 } 611 612 /** 613 * Return the last known country ISO before device is not camping on a network 614 * (e.g. Airplane Mode) 615 * 616 * @return The device's last known country ISO. 617 */ 618 @NonNull getLastKnownCountryIso()619 public String getLastKnownCountryIso() { 620 final SharedPreferences prefs = mPhone.getContext().getSharedPreferences( 621 LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, Context.MODE_PRIVATE); 622 return prefs.getString(LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, ""); 623 } 624 log(String msg)625 private void log(String msg) { 626 Rlog.d(mTag, msg); 627 } 628 loge(String msg)629 private void loge(String msg) { 630 Rlog.e(mTag, msg); 631 } 632 633 /** 634 * Print the DeviceStateMonitor into the given stream. 635 * 636 * @param fd The raw file descriptor that the dump is being sent to. 637 * @param pw A PrintWriter to which the dump is to be set. 638 * @param args Additional arguments to the dump request. 639 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)640 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 641 final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); 642 pw.println("LocaleTracker-" + mPhone.getPhoneId() + ":"); 643 ipw.increaseIndent(); 644 ipw.println("mIsTracking = " + mIsTracking); 645 ipw.println("mOperatorNumeric = " + mOperatorNumeric); 646 ipw.println("mSimState = " + mSimState); 647 ipw.println("mCellInfoList = " + mCellInfoList); 648 ipw.println("mCurrentCountryIso = " + mCurrentCountryIso); 649 ipw.println("mFailCellInfoCount = " + mFailCellInfoCount); 650 ipw.println("Local logs:"); 651 ipw.increaseIndent(); 652 mLocalLog.dump(fd, ipw, args); 653 ipw.decreaseIndent(); 654 ipw.decreaseIndent(); 655 ipw.flush(); 656 } 657 } 658