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