| /* |
| * Copyright 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.telephony; |
| |
| import static android.text.format.DateUtils.MINUTE_IN_MILLIS; |
| import static android.text.format.DateUtils.SECOND_IN_MILLIS; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.SharedPreferences; |
| import android.os.AsyncResult; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.UserHandle; |
| import android.sysprop.TelephonyProperties; |
| import android.telephony.CellInfo; |
| import android.telephony.ServiceState; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| import android.util.LocalLog; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.MccTable.MccMnc; |
| import com.android.internal.telephony.flags.FeatureFlags; |
| import com.android.internal.telephony.util.TelephonyUtils; |
| import com.android.internal.util.IndentingPrintWriter; |
| import com.android.telephony.Rlog; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| |
| /** |
| * The locale tracker keeps tracking the current locale of the phone. |
| */ |
| public class LocaleTracker extends Handler { |
| private static final boolean DBG = true; |
| |
| /** Event for getting cell info from the modem */ |
| private static final int EVENT_REQUEST_CELL_INFO = 1; |
| |
| /** Event for service state changed */ |
| private static final int EVENT_SERVICE_STATE_CHANGED = 2; |
| |
| /** Event for sim state changed */ |
| private static final int EVENT_SIM_STATE_CHANGED = 3; |
| |
| /** Event for incoming unsolicited cell info */ |
| private static final int EVENT_UNSOL_CELL_INFO = 4; |
| |
| /** Event for incoming cell info */ |
| private static final int EVENT_RESPONSE_CELL_INFO = 5; |
| |
| /** Event to fire if the operator from ServiceState is considered truly lost */ |
| private static final int EVENT_OPERATOR_LOST = 6; |
| |
| /** Event to override the current locale */ |
| private static final int EVENT_OVERRIDE_LOCALE = 7; |
| |
| /** |
| * The broadcast intent action to override the current country for testing purposes |
| * |
| * <p> This broadcast is not effective on user build. |
| * |
| * <p>Example: To override the current country <code> |
| * adb root |
| * adb shell am broadcast -a com.android.internal.telephony.action.COUNTRY_OVERRIDE |
| * --es country us </code> |
| * |
| * <p> To remove the override <code> |
| * adb root |
| * adb shell am broadcast -a com.android.internal.telephony.action.COUNTRY_OVERRIDE |
| * --ez reset true</code> |
| */ |
| private static final String ACTION_COUNTRY_OVERRIDE = |
| "com.android.internal.telephony.action.COUNTRY_OVERRIDE"; |
| |
| /** The extra for country override */ |
| private static final String EXTRA_COUNTRY = "country"; |
| |
| /** The extra for country override reset */ |
| private static final String EXTRA_RESET = "reset"; |
| |
| // Todo: Read this from Settings. |
| /** The minimum delay to get cell info from the modem */ |
| private static final long CELL_INFO_MIN_DELAY_MS = 2 * SECOND_IN_MILLIS; |
| |
| // Todo: Read this from Settings. |
| /** The maximum delay to get cell info from the modem */ |
| private static final long CELL_INFO_MAX_DELAY_MS = 10 * MINUTE_IN_MILLIS; |
| |
| // Todo: Read this from Settings. |
| /** The delay for periodically getting cell info from the modem */ |
| private static final long CELL_INFO_PERIODIC_POLLING_DELAY_MS = 10 * MINUTE_IN_MILLIS; |
| |
| /** |
| * The delay after the last time the device camped on a cell before declaring that the |
| * ServiceState's MCC information can no longer be used (and thus kicking in the CellInfo |
| * based tracking. |
| */ |
| private static final long SERVICE_OPERATOR_LOST_DELAY_MS = 10 * MINUTE_IN_MILLIS; |
| |
| /** The maximum fail count to prevent delay time overflow */ |
| private static final int MAX_FAIL_COUNT = 30; |
| |
| /** The last known country iso */ |
| private static final String LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY = |
| "last_known_country_iso"; |
| |
| private String mTag; |
| |
| private final Phone mPhone; |
| |
| private final NitzStateMachine mNitzStateMachine; |
| |
| /** SIM card state. Must be one of TelephonyManager.SIM_STATE_XXX */ |
| private int mSimState; |
| |
| /** Current serving PLMN's MCC/MNC */ |
| @Nullable |
| private String mOperatorNumeric; |
| |
| /** Current cell tower information */ |
| @Nullable |
| private List<CellInfo> mCellInfoList; |
| |
| /** Count of invalid cell info we've got so far. Will reset once we get a successful one */ |
| private int mFailCellInfoCount; |
| |
| /** The ISO-3166 two-letter code of device's current country */ |
| @Nullable |
| private String mCurrentCountryIso; |
| |
| @NonNull private final FeatureFlags mFeatureFlags; |
| |
| /** The country override for testing purposes */ |
| @Nullable |
| private String mCountryOverride; |
| |
| /** Current service state. Must be one of ServiceState.STATE_XXX. */ |
| private int mLastServiceState = ServiceState.STATE_POWER_OFF; |
| |
| private boolean mIsTracking = false; |
| |
| private final LocalLog mLocalLog = new LocalLog(32, false /* useLocalTimestamps */); |
| |
| /** Broadcast receiver to get SIM card state changed event */ |
| private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED.equals(intent.getAction())) { |
| int phoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY, 0); |
| if (phoneId == mPhone.getPhoneId()) { |
| obtainMessage(EVENT_SIM_STATE_CHANGED, |
| intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE, |
| TelephonyManager.SIM_STATE_UNKNOWN), 0).sendToTarget(); |
| } |
| } else if (ACTION_COUNTRY_OVERRIDE.equals(intent.getAction())) { |
| // note: need to set ServiceStateTracker#PROP_FORCE_ROAMING to force roaming. |
| String countryOverride = intent.getStringExtra(EXTRA_COUNTRY); |
| boolean reset = intent.getBooleanExtra(EXTRA_RESET, false); |
| if (reset) countryOverride = null; |
| log("Received country override: " + countryOverride); |
| // countryOverride null to reset the override. |
| obtainMessage(EVENT_OVERRIDE_LOCALE, countryOverride).sendToTarget(); |
| } |
| } |
| }; |
| |
| /** |
| * Message handler |
| * |
| * @param msg The message |
| */ |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case EVENT_REQUEST_CELL_INFO: |
| mPhone.requestCellInfoUpdate(null, obtainMessage(EVENT_RESPONSE_CELL_INFO)); |
| break; |
| |
| case EVENT_UNSOL_CELL_INFO: |
| processCellInfo((AsyncResult) msg.obj); |
| // If the unsol happened to be useful, use it; otherwise, pretend it didn't happen. |
| if (mCellInfoList != null && mCellInfoList.size() > 0) requestNextCellInfo(true); |
| break; |
| |
| case EVENT_RESPONSE_CELL_INFO: |
| processCellInfo((AsyncResult) msg.obj); |
| // If the cellInfo was non-empty then it's business as usual. Either way, this |
| // cell info was requested by us, so it's our trigger to schedule another one. |
| requestNextCellInfo(mCellInfoList != null && mCellInfoList.size() > 0); |
| break; |
| |
| case EVENT_SERVICE_STATE_CHANGED: |
| AsyncResult ar = (AsyncResult) msg.obj; |
| onServiceStateChanged((ServiceState) ar.result); |
| break; |
| |
| case EVENT_SIM_STATE_CHANGED: |
| onSimCardStateChanged(msg.arg1); |
| break; |
| |
| case EVENT_OPERATOR_LOST: |
| updateOperatorNumericImmediate(""); |
| updateTrackingStatus(); |
| break; |
| |
| case EVENT_OVERRIDE_LOCALE: |
| mCountryOverride = (String) msg.obj; |
| updateLocale(); |
| break; |
| |
| default: |
| throw new IllegalStateException("Unexpected message arrives. msg = " + msg.what); |
| } |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param phone The phone object |
| * @param nitzStateMachine NITZ state machine |
| * @param looper The looper message handler |
| */ |
| public LocaleTracker(Phone phone, NitzStateMachine nitzStateMachine, Looper looper, |
| FeatureFlags featureFlags) { |
| super(looper); |
| mPhone = phone; |
| mNitzStateMachine = nitzStateMachine; |
| mSimState = TelephonyManager.SIM_STATE_UNKNOWN; |
| mTag = LocaleTracker.class.getSimpleName() + "-" + mPhone.getPhoneId(); |
| mFeatureFlags = featureFlags; |
| |
| final IntentFilter filter = new IntentFilter(); |
| filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED); |
| if (TelephonyUtils.IS_DEBUGGABLE) { |
| filter.addAction(ACTION_COUNTRY_OVERRIDE); |
| } |
| mPhone.getContext().registerReceiver(mBroadcastReceiver, filter); |
| |
| mPhone.registerForServiceStateChanged(this, EVENT_SERVICE_STATE_CHANGED, null); |
| mPhone.registerForCellInfo(this, EVENT_UNSOL_CELL_INFO, null); |
| } |
| |
| /** |
| * Get the device's current country. |
| * |
| * @return The device's current country. Empty string if the information is not available. |
| */ |
| @NonNull |
| public String getCurrentCountry() { |
| return (mCurrentCountryIso != null) ? mCurrentCountryIso : ""; |
| } |
| |
| /** |
| * Get the MCC from cell tower information. |
| * |
| * @return MCC in string format. Null if the information is not available. |
| */ |
| @Nullable |
| private String getMccFromCellInfo() { |
| String selectedMcc = null; |
| if (mCellInfoList != null) { |
| Map<String, Integer> mccMap = new HashMap<>(); |
| int maxCount = 0; |
| for (CellInfo cellInfo : mCellInfoList) { |
| String mcc = cellInfo.getCellIdentity().getMccString(); |
| if (mcc != null) { |
| int count = 1; |
| if (mccMap.containsKey(mcc)) { |
| count = mccMap.get(mcc) + 1; |
| } |
| mccMap.put(mcc, count); |
| // This is unlikely, but if MCC from cell info looks different, we choose the |
| // MCC that occurs most. |
| if (count > maxCount) { |
| maxCount = count; |
| selectedMcc = mcc; |
| } |
| } |
| } |
| } |
| return selectedMcc; |
| } |
| |
| /** |
| * Get the most frequent MCC + MNC combination with the specified MCC using cell tower |
| * information. If no one combination is more frequent than any other an arbitrary MCC + MNC is |
| * returned with the matching MCC. The MNC value returned can be null if it is not provided by |
| * the cell tower information. |
| * |
| * @param mccToMatch the MCC to match |
| * @return a matching {@link MccMnc}. Null if the information is not available. |
| */ |
| @Nullable |
| private MccMnc getMccMncFromCellInfo(@NonNull String mccToMatch) { |
| MccMnc selectedMccMnc = null; |
| if (mCellInfoList != null) { |
| Map<MccMnc, Integer> mccMncMap = new HashMap<>(); |
| int maxCount = 0; |
| for (CellInfo cellInfo : mCellInfoList) { |
| String mcc = cellInfo.getCellIdentity().getMccString(); |
| if (Objects.equals(mcc, mccToMatch)) { |
| String mnc = cellInfo.getCellIdentity().getMncString(); |
| MccMnc mccMnc = new MccMnc(mcc, mnc); |
| int count = 1; |
| if (mccMncMap.containsKey(mccMnc)) { |
| count = mccMncMap.get(mccMnc) + 1; |
| } |
| mccMncMap.put(mccMnc, count); |
| // We keep track of the MCC+MNC combination that occurs most frequently, if |
| // there is one. A null MNC is treated like any other distinct MCC+MNC |
| // combination. |
| if (count > maxCount) { |
| maxCount = count; |
| selectedMccMnc = mccMnc; |
| } |
| } |
| } |
| } |
| return selectedMccMnc; |
| } |
| |
| /** |
| * Called when SIM card state changed. Only when we absolutely know the SIM is absent, we get |
| * cell info from the network. Other SIM states like NOT_READY might be just a transitioning |
| * state. |
| * |
| * @param state SIM card state. Must be one of TelephonyManager.SIM_STATE_XXX. |
| */ |
| private void onSimCardStateChanged(int state) { |
| mSimState = state; |
| updateLocale(); |
| updateTrackingStatus(); |
| } |
| |
| /** |
| * Called when service state changed. |
| * |
| * @param serviceState Service state |
| */ |
| private void onServiceStateChanged(ServiceState serviceState) { |
| mLastServiceState = serviceState.getState(); |
| updateLocale(); |
| updateTrackingStatus(); |
| } |
| |
| /** |
| * Update MCC/MNC from network service state. |
| * |
| * @param operatorNumeric MCC/MNC of the operator |
| */ |
| public void updateOperatorNumeric(String operatorNumeric) { |
| if (TextUtils.isEmpty(operatorNumeric)) { |
| if (!hasMessages(EVENT_OPERATOR_LOST)) { |
| sendMessageDelayed(obtainMessage(EVENT_OPERATOR_LOST), |
| SERVICE_OPERATOR_LOST_DELAY_MS); |
| } |
| } else { |
| removeMessages(EVENT_OPERATOR_LOST); |
| updateOperatorNumericImmediate(operatorNumeric); |
| } |
| } |
| |
| private void updateOperatorNumericImmediate(String operatorNumeric) { |
| // Check if the operator numeric changes. |
| if (!operatorNumeric.equals(mOperatorNumeric)) { |
| String msg = "Operator numeric changes to \"" + operatorNumeric + "\""; |
| if (DBG) log(msg); |
| mLocalLog.log(msg); |
| mOperatorNumeric = operatorNumeric; |
| updateLocale(); |
| } |
| } |
| |
| private void processCellInfo(AsyncResult ar) { |
| if (ar == null || ar.exception != null) { |
| mCellInfoList = null; |
| return; |
| } |
| List<CellInfo> cellInfoList = (List<CellInfo>) ar.result; |
| String msg = "processCellInfo: cell info=" + cellInfoList; |
| if (DBG) log(msg); |
| mCellInfoList = cellInfoList; |
| updateLocale(); |
| } |
| |
| private void requestNextCellInfo(boolean succeeded) { |
| if (!mIsTracking) return; |
| |
| removeMessages(EVENT_REQUEST_CELL_INFO); |
| if (succeeded) { |
| resetCellInfoRetry(); |
| // Now we need to get the cell info from the modem periodically |
| // even if we already got the cell info because the user can move. |
| removeMessages(EVENT_UNSOL_CELL_INFO); |
| removeMessages(EVENT_RESPONSE_CELL_INFO); |
| sendMessageDelayed(obtainMessage(EVENT_REQUEST_CELL_INFO), |
| CELL_INFO_PERIODIC_POLLING_DELAY_MS); |
| } else { |
| // If we can't get a valid cell info. Try it again later. |
| long delay = getCellInfoDelayTime(++mFailCellInfoCount); |
| if (DBG) log("Can't get cell info. Try again in " + delay / 1000 + " secs."); |
| sendMessageDelayed(obtainMessage(EVENT_REQUEST_CELL_INFO), delay); |
| } |
| } |
| |
| /** |
| * Get the delay time to get cell info from modem. The delay time grows exponentially to prevent |
| * battery draining. |
| * |
| * @param failCount Count of invalid cell info we've got so far. |
| * @return The delay time for next get cell info |
| */ |
| @VisibleForTesting |
| public static long getCellInfoDelayTime(int failCount) { |
| // Exponentially grow the delay time. Note we limit the fail count to MAX_FAIL_COUNT to |
| // prevent overflow in Math.pow(). |
| long delay = CELL_INFO_MIN_DELAY_MS |
| * (long) Math.pow(2, Math.min(failCount, MAX_FAIL_COUNT) - 1); |
| return Math.min(Math.max(delay, CELL_INFO_MIN_DELAY_MS), CELL_INFO_MAX_DELAY_MS); |
| } |
| |
| /** |
| * Stop retrying getting cell info from the modem. It cancels any scheduled cell info retrieving |
| * request. |
| */ |
| private void resetCellInfoRetry() { |
| mFailCellInfoCount = 0; |
| removeMessages(EVENT_REQUEST_CELL_INFO); |
| } |
| |
| private void updateTrackingStatus() { |
| boolean shouldTrackLocale = |
| (mSimState == TelephonyManager.SIM_STATE_ABSENT |
| || TextUtils.isEmpty(mOperatorNumeric)) |
| && (mLastServiceState == ServiceState.STATE_OUT_OF_SERVICE |
| || mLastServiceState == ServiceState.STATE_EMERGENCY_ONLY); |
| if (shouldTrackLocale) { |
| startTracking(); |
| } else { |
| stopTracking(); |
| } |
| } |
| |
| private void stopTracking() { |
| if (!mIsTracking) return; |
| mIsTracking = false; |
| String msg = "Stopping LocaleTracker"; |
| if (DBG) log(msg); |
| mLocalLog.log(msg); |
| mCellInfoList = null; |
| resetCellInfoRetry(); |
| } |
| |
| private void startTracking() { |
| if (mIsTracking) return; |
| String msg = "Starting LocaleTracker"; |
| mLocalLog.log(msg); |
| if (DBG) log(msg); |
| mIsTracking = true; |
| sendMessage(obtainMessage(EVENT_REQUEST_CELL_INFO)); |
| } |
| |
| /** |
| * Update the device's current locale |
| */ |
| private synchronized void updateLocale() { |
| // If MCC is available from network service state, use it first. |
| String countryIso = ""; |
| String countryIsoDebugInfo = "empty as default"; |
| |
| if (!TextUtils.isEmpty(mOperatorNumeric)) { |
| MccMnc mccMnc = MccMnc.fromOperatorNumeric(mOperatorNumeric); |
| if (mccMnc != null) { |
| countryIso = MccTable.geoCountryCodeForMccMnc(mccMnc); |
| countryIsoDebugInfo = "OperatorNumeric(" + mOperatorNumeric |
| + "): MccTable.geoCountryCodeForMccMnc(\"" + mccMnc + "\")"; |
| } else { |
| loge("updateLocale: Can't get country from operator numeric. mOperatorNumeric = " |
| + mOperatorNumeric); |
| } |
| } |
| |
| // If for any reason we can't get country from operator numeric, try to get it from cell |
| // info. |
| if (TextUtils.isEmpty(countryIso)) { |
| // Find the most prevalent MCC from surrounding cell towers. |
| String mcc = getMccFromCellInfo(); |
| if (mcc != null) { |
| countryIso = MccTable.countryCodeForMcc(mcc); |
| countryIsoDebugInfo = "CellInfo: MccTable.countryCodeForMcc(\"" + mcc + "\")"; |
| |
| // Some MCC+MNC combinations are known to be used in countries other than those |
| // that the MCC alone would suggest. Do a second pass of nearby cells that match |
| // the most frequently observed MCC to see if this could be one of those cases. |
| MccMnc mccMnc = getMccMncFromCellInfo(mcc); |
| if (mccMnc != null) { |
| countryIso = MccTable.geoCountryCodeForMccMnc(mccMnc); |
| countryIsoDebugInfo = |
| "CellInfo: MccTable.geoCountryCodeForMccMnc(" + mccMnc + ")"; |
| } |
| } |
| } |
| |
| if (mCountryOverride != null) { |
| countryIso = mCountryOverride; |
| countryIsoDebugInfo = "mCountryOverride = \"" + mCountryOverride + "\""; |
| } |
| |
| if (!mPhone.isRadioOn()) { |
| countryIso = ""; |
| countryIsoDebugInfo = "radio off"; |
| |
| // clear cell infos, we don't know where the next network to camp on. |
| mCellInfoList = null; |
| } |
| |
| log("updateLocale: countryIso = " + countryIso |
| + ", countryIsoDebugInfo = " + countryIsoDebugInfo); |
| if (!Objects.equals(countryIso, mCurrentCountryIso)) { |
| String msg = "updateLocale: Change the current country to \"" + countryIso + "\"" |
| + ", countryIsoDebugInfo = " + countryIsoDebugInfo |
| + ", mCellInfoList = " + mCellInfoList; |
| log(msg); |
| mLocalLog.log(msg); |
| mCurrentCountryIso = countryIso; |
| |
| // Update the last known country ISO |
| if (!TextUtils.isEmpty(mCurrentCountryIso)) { |
| updateLastKnownCountryIso(mCurrentCountryIso); |
| } |
| |
| int phoneId = mPhone.getPhoneId(); |
| if (SubscriptionManager.isValidPhoneId(phoneId)) { |
| List<String> newProp = new ArrayList<>( |
| TelephonyProperties.operator_iso_country()); |
| while (newProp.size() <= phoneId) newProp.add(null); |
| newProp.set(phoneId, mCurrentCountryIso); |
| TelephonyProperties.operator_iso_country(newProp); |
| } |
| |
| if (mFeatureFlags.oemEnabledSatelliteFlag()) { |
| TelephonyCountryDetector.getInstance(mPhone.getContext()) |
| .onNetworkCountryCodeChanged(mPhone, countryIso); |
| } |
| Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED); |
| intent.putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, countryIso); |
| intent.putExtra(TelephonyManager.EXTRA_LAST_KNOWN_NETWORK_COUNTRY, |
| getLastKnownCountryIso()); |
| SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId()); |
| mPhone.getContext().sendBroadcastAsUser(intent, UserHandle.ALL); |
| } |
| |
| // Pass the geographical country information to the telephony time zone detection code. |
| |
| String timeZoneCountryIso = countryIso; |
| String timeZoneCountryIsoDebugInfo = countryIsoDebugInfo; |
| boolean isTestMcc = false; |
| if (!TextUtils.isEmpty(mOperatorNumeric)) { |
| // For a test cell (MCC 001), the NitzStateMachine requires handleCountryDetected("") in |
| // order to pass compliance tests. http://b/142840879 |
| if (mOperatorNumeric.startsWith("001")) { |
| isTestMcc = true; |
| timeZoneCountryIso = ""; |
| timeZoneCountryIsoDebugInfo = "Test cell: " + mOperatorNumeric; |
| } |
| } |
| log("updateLocale: timeZoneCountryIso = " + timeZoneCountryIso |
| + ", timeZoneCountryIsoDebugInfo = " + timeZoneCountryIsoDebugInfo); |
| |
| if (TextUtils.isEmpty(timeZoneCountryIso) && !isTestMcc) { |
| mNitzStateMachine.handleCountryUnavailable(); |
| } else { |
| mNitzStateMachine.handleCountryDetected(timeZoneCountryIso); |
| } |
| } |
| |
| /** Exposed for testing purposes */ |
| public boolean isTracking() { |
| return mIsTracking; |
| } |
| |
| private void updateLastKnownCountryIso(String countryIso) { |
| if (!TextUtils.isEmpty(countryIso)) { |
| final SharedPreferences prefs = mPhone.getContext().getSharedPreferences( |
| LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, Context.MODE_PRIVATE); |
| final SharedPreferences.Editor editor = prefs.edit(); |
| editor.putString(LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, countryIso); |
| editor.commit(); |
| log("update country iso in sharedPrefs " + countryIso); |
| } |
| } |
| |
| /** |
| * Return the last known country ISO before device is not camping on a network |
| * (e.g. Airplane Mode) |
| * |
| * @return The device's last known country ISO. |
| */ |
| @NonNull |
| public String getLastKnownCountryIso() { |
| final SharedPreferences prefs = mPhone.getContext().getSharedPreferences( |
| LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, Context.MODE_PRIVATE); |
| return prefs.getString(LAST_KNOWN_COUNTRY_ISO_SHARED_PREFS_KEY, ""); |
| } |
| |
| private void log(String msg) { |
| Rlog.d(mTag, msg); |
| } |
| |
| private void loge(String msg) { |
| Rlog.e(mTag, msg); |
| } |
| |
| /** |
| * Print the DeviceStateMonitor into the given stream. |
| * |
| * @param fd The raw file descriptor that the dump is being sent to. |
| * @param pw A PrintWriter to which the dump is to be set. |
| * @param args Additional arguments to the dump request. |
| */ |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); |
| pw.println("LocaleTracker-" + mPhone.getPhoneId() + ":"); |
| ipw.increaseIndent(); |
| ipw.println("mIsTracking = " + mIsTracking); |
| ipw.println("mOperatorNumeric = " + mOperatorNumeric); |
| ipw.println("mSimState = " + mSimState); |
| ipw.println("mCellInfoList = " + mCellInfoList); |
| ipw.println("mCurrentCountryIso = " + mCurrentCountryIso); |
| ipw.println("mFailCellInfoCount = " + mFailCellInfoCount); |
| ipw.println("Local logs:"); |
| ipw.increaseIndent(); |
| mLocalLog.dump(fd, ipw, args); |
| ipw.decreaseIndent(); |
| ipw.decreaseIndent(); |
| ipw.flush(); |
| } |
| |
| /** |
| * This getter should only be used for testing purposes in classes that wish to spoof the |
| * country ISO. An example of how this can be done is in ServiceStateTracker#InSameCountry |
| * @return spoofed country iso. |
| */ |
| @VisibleForTesting |
| public String getCountryOverride() { |
| return mCountryOverride; |
| } |
| } |