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