Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a0b9d20b authored by Jack Yu's avatar Jack Yu Committed by Android (Google) Code Review
Browse files

Merge "DO NOT MERGE - Set country code even when device is not registered on network" into pi-dev

parents bb4142bb 199f4fd9
Loading
Loading
Loading
Loading
+353 −0
Original line number Diff line number Diff line
/*
 * 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.HOUR_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.net.wifi.WifiManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.telephony.CellInfo;
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoLte;
import android.telephony.CellInfoWcdma;
import android.telephony.Rlog;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.LocalLog;

import com.android.internal.util.CollectionUtils;
import com.android.internal.util.IndentingPrintWriter;

import java.io.FileDescriptor;
import java.io.PrintWriter;
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;
    private static final String TAG = LocaleTracker.class.getSimpleName();

    /** Event to trigger get cell info from the modem */
    private static final int EVENT_GET_CELL_INFO = 1;

    // 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 = 1 * HOUR_IN_MILLIS;

    private final Phone mPhone;

    /** 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> mCellInfo;

    /** Count of invalid cell info we've got so far. Will reset once we get a successful one */
    private int mFailCellInfoCount;

    /** The ISO-3166 code of device's current country */
    @Nullable
    private String mCurrentCountryIso;

    private final LocalLog mLocalLog = new LocalLog(50);

    /** 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()) {
                    onSimCardStateChanged(intent.getIntExtra(TelephonyManager.EXTRA_SIM_STATE,
                            TelephonyManager.SIM_STATE_UNKNOWN));
                }
            }
        }
    };

    /**
     * Message handler
     *
     * @param msg The message
     */
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case EVENT_GET_CELL_INFO:
                synchronized (this) {
                    getCellInfo();
                    updateLocale();
                }
                break;
            default:
                throw new IllegalStateException("Unexpected message arrives. msg = " + msg.what);
        }
    }

    /**
     * Constructor
     *
     * @param phone The phone object
     * @param looper The looper message handler
     */
    public LocaleTracker(Phone phone, Looper looper)  {
        super(looper);
        mPhone = phone;
        mSimState = TelephonyManager.SIM_STATE_UNKNOWN;

        final IntentFilter filter = new IntentFilter();
        filter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
        mPhone.getContext().registerReceiver(mBroadcastReceiver, filter);
    }

    /**
     * Get the device's current country.
     *
     * @return The device's current country. Empty string if the information is not available.
     */
    @NonNull
    public synchronized 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 (mCellInfo != null) {
            Map<String, Integer> countryCodeMap = new HashMap<>();
            int maxCount = 0;
            for (CellInfo cellInfo : mCellInfo) {
                String mcc = null;
                if (cellInfo instanceof CellInfoGsm) {
                    mcc = ((CellInfoGsm) cellInfo).getCellIdentity().getMccString();
                } else if (cellInfo instanceof CellInfoLte) {
                    mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMccString();
                } else if (cellInfo instanceof CellInfoWcdma) {
                    mcc = ((CellInfoWcdma) cellInfo).getCellIdentity().getMccString();
                }
                if (mcc != null) {
                    int count = 1;
                    if (countryCodeMap.containsKey(mcc)) {
                        count = countryCodeMap.get(mcc) + 1;
                    }
                    countryCodeMap.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;
    }

    /**
     * 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 synchronized void onSimCardStateChanged(int state) {
        if (mSimState != state && state == TelephonyManager.SIM_STATE_ABSENT) {
            if (DBG) log("Sim absent. Get latest cell info from the modem.");
            getCellInfo();
            updateLocale();
        }
        mSimState = state;
    }

    /**
     * Update MCC/MNC from network service state.
     *
     * @param operatorNumeric MCC/MNC of the operator
     */
    public synchronized void updateOperatorNumeric(String operatorNumeric) {
        // Check if the operator numeric changes.
        String msg = "updateOperatorNumeric. mcc/mnc=" + operatorNumeric;
        if (DBG) log(msg);
        mLocalLog.log(msg);
        if (!Objects.equals(mOperatorNumeric, operatorNumeric)) {
            if (DBG) {
                log("onUpdateOperatorNumeric: operator numeric changes to " + operatorNumeric);
            }
            mOperatorNumeric = operatorNumeric;

            // If the operator numeric becomes unavailable, we need to get the latest cell info so
            // that we can get MCC from it.
            if (TextUtils.isEmpty(mOperatorNumeric)) {
                if (DBG) {
                    log("Operator numeric unavailable. Get latest cell info from the modem.");
                }
                getCellInfo();
            }
            updateLocale();
        }
    }

    /**
     * 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
     */
    private long getCellInfoDelayTime(int failCount) {
        // Exponentially grow the delay time
        long delay = CELL_INFO_MIN_DELAY_MS * (long) Math.pow(2, failCount - 1);
        if (delay < CELL_INFO_MIN_DELAY_MS) {
            delay = CELL_INFO_MIN_DELAY_MS;
        } else if (delay > CELL_INFO_MAX_DELAY_MS) {
            delay = CELL_INFO_MAX_DELAY_MS;
        }
        return delay;
    }

    /**
     * Get cell info from the modem.
     */
    private void getCellInfo() {
        // Get all cell info. Passing null to use default worksource, which indicates the original
        // request is from telephony internally.
        mCellInfo = mPhone.getAllCellInfo(null);
        String msg = "getCellInfo: cell info=" + mCellInfo;
        if (DBG) log(msg);
        mLocalLog.log(msg);
        if (CollectionUtils.isEmpty(mCellInfo)) {
            // 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_GET_CELL_INFO), delay);
        } else {
            mFailCellInfoCount = 0;
            // We successfully got cell info from the modem. Cancel the queued get cell info event
            // if there is any.
            removeMessages(EVENT_GET_CELL_INFO);
        }
    }

    /**
     * Update the device's current locale
     */
    private void updateLocale() {
        // If MCC is available from network service state, use it first.
        String mcc = null;
        String countryIso = null;
        if (!TextUtils.isEmpty(mOperatorNumeric)) {
            try {
                mcc = mOperatorNumeric.substring(0, 3);
                countryIso = MccTable.countryCodeForMcc(Integer.parseInt(mcc));
            } catch (StringIndexOutOfBoundsException | NumberFormatException ex) {
                loge("updateLocale: Can't get country from operator numeric. mcc = "
                        + mcc + ". ex=" + ex);
            }
        }

        // If for any reason we can't get country from operator numeric, try to get it from cell
        // info.
        if (TextUtils.isEmpty(countryIso)) {
            mcc = getMccFromCellInfo();
            if (!TextUtils.isEmpty(mcc)) {
                try {
                    countryIso = MccTable.countryCodeForMcc(Integer.parseInt(mcc));
                } catch (NumberFormatException ex) {
                    loge("updateLocale: Can't get country from cell info. mcc = "
                            + mcc + ". ex=" + ex);
                }
            }
        }

        String msg = "updateLocale: mcc = " + mcc + ", country = " + countryIso;
        log(msg);
        mLocalLog.log(msg);
        if (!Objects.equals(countryIso, mCurrentCountryIso)) {
            log("updateLocale: Change the current country to " + countryIso);
            mCurrentCountryIso = countryIso;

            TelephonyManager.setTelephonyProperty(mPhone.getPhoneId(),
                    TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, mCurrentCountryIso);

            // Set the country code for wifi. This sets allowed wifi channels based on the
            // country of the carrier we see. If we can't see any, reset to 0 so we don't
            // broadcast on forbidden channels.
            ((WifiManager) mPhone.getContext().getSystemService(Context.WIFI_SERVICE))
                    .setCountryCode(countryIso);
        }
    }

    private void log(String msg) {
        Rlog.d(TAG, msg);
    }

    private void loge(String msg) {
        Rlog.e(TAG, 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:");
        ipw.increaseIndent();
        ipw.println("mOperatorNumeric = " + mOperatorNumeric);
        ipw.println("mSimState = " + mSimState);
        ipw.println("mCellInfo = " + mCellInfo);
        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();
    }
}
+24 −12
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.os.AsyncResult;
import android.os.BaseBundle;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.PersistableBundle;
import android.os.Registrant;
@@ -260,6 +261,9 @@ public class ServiceStateTracker extends Handler {

    private final RatRatcheter mRatRatcheter;

    private final HandlerThread mHandlerThread;
    private final LocaleTracker mLocaleTracker;

    private final LocalLog mRoamingLog = new LocalLog(10);
    private final LocalLog mAttachLog = new LocalLog(10);
    private final LocalLog mPhoneTypeLog = new LocalLog(10);
@@ -509,6 +513,13 @@ public class ServiceStateTracker extends Handler {
                    this, EVENT_NETWORK_STATE_CHANGED, null);
        }

        // Create a new handler thread dedicated for locale tracker because the blocking
        // getAllCellInfo call requires clients calling from a different thread.
        mHandlerThread = new HandlerThread(LocaleTracker.class.getSimpleName());
        mHandlerThread.start();
        mLocaleTracker = TelephonyComponentFactory.getInstance().makeLocaleTracker(
                mPhone, mHandlerThread.getLooper());

        mCi.registerForImsNetworkStateChanged(this, EVENT_IMS_STATE_CHANGED, null);
        mCi.registerForRadioStateChanged(this, EVENT_RADIO_STATE_CHANGED, null);
        mCi.setOnNITZTime(this, EVENT_NITZ_TIME, null);
@@ -656,6 +667,7 @@ public class ServiceStateTracker extends Handler {
        mCi.unregisterForPhysicalChannelConfiguration(this);
        mSubscriptionManager
            .removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
        mHandlerThread.quit();
        mCi.unregisterForImsNetworkStateChanged(this);
        mPhone.getCarrierActionAgent().unregisterForCarrierAction(this,
                CARRIER_ACTION_SET_RADIO_ENABLED);
@@ -2852,11 +2864,12 @@ public class ServiceStateTracker extends Handler {
            }

            tm.setNetworkOperatorNumericForPhone(mPhone.getPhoneId(), operatorNumeric);
            updateCarrierMccMncConfiguration(operatorNumeric,
                    prevOperatorNumeric, mPhone.getContext());

            if (isInvalidOperatorNumeric(operatorNumeric)) {
                if (DBG) log("operatorNumeric " + operatorNumeric + " is invalid");
                tm.setNetworkCountryIsoForPhone(mPhone.getPhoneId(), "");
                // Passing empty string is important for the first update. The initial value of
                // operator numeric in locale tracker is null.
                mLocaleTracker.updateOperatorNumeric("");
                mNitzState.handleNetworkUnavailable();
            } else if (mSS.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
                // If the device is on IWLAN, modems manufacture a ServiceState with the MCC/MNC of
@@ -2868,15 +2881,8 @@ public class ServiceStateTracker extends Handler {
                    setOperatorIdd(operatorNumeric);
                }

                // Update ISO.
                String countryIsoCode = "";
                try {
                    String mcc = operatorNumeric.substring(0, 3);
                    countryIsoCode = MccTable.countryCodeForMcc(Integer.parseInt(mcc));
                } catch (NumberFormatException | StringIndexOutOfBoundsException ex) {
                    loge("pollStateDone: countryCodeForMcc error: " + ex);
                }
                tm.setNetworkCountryIsoForPhone(mPhone.getPhoneId(), countryIsoCode);
                mLocaleTracker.updateOperatorNumeric(operatorNumeric);
                String countryIsoCode = mLocaleTracker.getCurrentCountry();

                // Update Time Zone.
                boolean iccCardExists = iccCardExists();
@@ -4338,6 +4344,8 @@ public class ServiceStateTracker extends Handler {
        pw.println(" mLteRsrpBoost=" + mLteRsrpBoost);
        dumpEarfcnPairList(pw);

        mLocaleTracker.dump(fd, pw, args);

        pw.println(" Roaming Log:");
        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
        ipw.increaseIndent();
@@ -4657,4 +4665,8 @@ public class ServiceStateTracker extends Handler {
        // Return static default defined in CarrierConfigManager.
        return CarrierConfigManager.getDefaultConfig();
    }

    public LocaleTracker getLocaleTracker() {
        return mLocaleTracker;
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.Context;
import android.database.Cursor;
import android.os.Handler;
import android.os.IDeviceIdleController;
import android.os.Looper;
import android.os.ServiceManager;
import android.telephony.AccessNetworkConstants.TransportType;

@@ -172,4 +173,8 @@ public class TelephonyComponentFactory {
        return IDeviceIdleController.Stub.asInterface(
                ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
    }

    public LocaleTracker makeLocaleTracker(Phone phone, Looper looper) {
        return new LocaleTracker(phone, looper);
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.IDeviceIdleController;
import android.os.Looper;
import android.os.RegistrantList;
import android.os.ServiceManager;
import android.provider.BlockedNumberContract;
@@ -195,6 +196,8 @@ public abstract class TelephonyTest {
    protected RadioConfig mMockRadioConfig;
    @Mock
    protected SubscriptionInfoUpdater mSubInfoRecordUpdater;
    @Mock
    protected LocaleTracker mLocaleTracker;

    protected ImsCallProfile mImsCallProfile;
    protected TelephonyManager mTelephonyManager;
@@ -368,6 +371,8 @@ public abstract class TelephonyTest {
                .makeDeviceStateMonitor(nullable(Phone.class));
        doReturn(mNitzStateMachine).when(mTelephonyComponentFactory)
                .makeNitzStateMachine(nullable(GsmCdmaPhone.class));
        doReturn(mLocaleTracker).when(mTelephonyComponentFactory)
                .makeLocaleTracker(nullable(Phone.class), nullable(Looper.class));

        //mPhone
        doReturn(mContext).when(mPhone).getContext();