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