• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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