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

Commit 2d1845b3 authored by fionaxu's avatar fionaxu Committed by android-build-merger
Browse files

Merge "carrier identification" am: 0e179495

am: ed9bf0c3

Change-Id: Ib841f871a6d637c2d4933a8767358de2c1cfe915
parents d00546da ed9bf0c3
Loading
Loading
Loading
Loading
+569 −0
Original line number Diff line number Diff line
/*
 * Copyright 2017 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 android.content.Context;
import android.content.SharedPreferences;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.provider.Telephony;
import android.telephony.Rlog;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.LocalLog;
import android.util.Log;

import com.android.internal.telephony.uicc.IccRecords;
import com.android.internal.telephony.uicc.UiccController;
import com.android.internal.util.IndentingPrintWriter;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import static android.provider.Telephony.CarrierIdentification;

/**
 * CarrierIdentifier identifies the subscription carrier and returns a canonical carrier Id
 * and a user friendly carrier name. CarrierIdentifier reads subscription info and check against
 * all carrier matching rules stored in CarrierIdProvider. It is msim aware, each phone has a
 * dedicated CarrierIdentifier.
 */
public class CarrierIdentifier extends Handler {
    private static final String LOG_TAG = CarrierIdentifier.class.getSimpleName();
    private static final boolean DBG = true;
    private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);

    // events to trigger carrier identification
    private static final int SIM_LOAD_EVENT             = 1;
    private static final int SIM_ABSENT_EVENT           = 2;
    private static final int SPN_OVERRIDE_EVENT         = 3;
    private static final int ICC_CHANGED_EVENT          = 4;
    private static final int PREFER_APN_UPDATE_EVENT    = 5;
    private static final int CARRIER_ID_DB_UPDATE_EVENT = 6;

    private static final Uri CONTENT_URL_PREFER_APN = Uri.withAppendedPath(
            Telephony.Carriers.CONTENT_URI, "preferapn");
    private static final String OPERATOR_BRAND_OVERRIDE_PREFIX = "operator_branding_";
    private static final int INVALID_CARRIER_ID         = -1;

    // cached matching rules based mccmnc to speed up resolution
    private List<CarrierMatchingRule> mCarrierMatchingRulesOnMccMnc = new ArrayList<>();
    // cached carrier Id
    private int mCarrierId = INVALID_CARRIER_ID;
    // cached carrier name
    private String mCarrierName;
    // cached preferapn name
    private String mPreferApn;
    // cached service provider name. telephonyManager API returns empty string as default value.
    // some carriers need to target devices with Empty SPN. In that case, carrier matching rule
    // should specify "" spn explicitly.
    private String mSpn = "";

    private Context mContext;
    private Phone mPhone;
    private IccRecords mIccRecords;
    private final LocalLog mCarrierIdLocalLog = new LocalLog(20);
    private final TelephonyManager mTelephonyMgr;
    private final SubscriptionsChangedListener mOnSubscriptionsChangedListener =
            new SubscriptionsChangedListener();
    private final SharedPreferenceChangedListener mSharedPrefListener =
            new SharedPreferenceChangedListener();

    private final ContentObserver mContentObserver = new ContentObserver(this) {
        @Override
        public void onChange(boolean selfChange, Uri uri) {
            logd("onChange URI: " + uri);
            if (CONTENT_URL_PREFER_APN.equals(uri.getLastPathSegment())) {
                sendEmptyMessage(PREFER_APN_UPDATE_EVENT);
            } else {
                sendEmptyMessage(CARRIER_ID_DB_UPDATE_EVENT);
            }
        }
    };

    private class SubscriptionsChangedListener
            extends SubscriptionManager.OnSubscriptionsChangedListener {
        final AtomicInteger mPreviousSubId =
                new AtomicInteger(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
        /**
         * Callback invoked when there is any change to any SubscriptionInfo. Typically
         * this method would invoke {@link SubscriptionManager#getActiveSubscriptionInfoList}
         */
        @Override
        public void onSubscriptionsChanged() {
            int subId = mPhone.getSubId();
            if (mPreviousSubId.getAndSet(subId) != subId) {
                if (DBG) {
                    logd("SubscriptionListener.onSubscriptionInfoChanged subId: "
                            + mPreviousSubId);
                }
                if (SubscriptionManager.isValidSubscriptionId(subId)) {
                    sendEmptyMessage(SIM_LOAD_EVENT);
                } else {
                    sendEmptyMessage(SIM_ABSENT_EVENT);
                }
            }
        }
    }

    private class SharedPreferenceChangedListener implements
            SharedPreferences.OnSharedPreferenceChangeListener {
        @Override
        public void onSharedPreferenceChanged(
                SharedPreferences sharedPreferences, String key) {
            if (TextUtils.equals(key, OPERATOR_BRAND_OVERRIDE_PREFIX
                    + mPhone.getIccSerialNumber())) {
                // SPN override from carrier privileged apps
                logd("[onSharedPreferenceChanged]: " + key);
                sendEmptyMessage(SPN_OVERRIDE_EVENT);
            }
        }
    }

    public CarrierIdentifier(Phone phone) {
        logd("Creating CarrierIdentifier[" + phone.getPhoneId() + "]");
        mContext = phone.getContext();
        mPhone = phone;
        mTelephonyMgr = TelephonyManager.from(mContext);

        // register events
        mContext.getContentResolver().registerContentObserver(CONTENT_URL_PREFER_APN, false,
                mContentObserver);
        mContext.getContentResolver().registerContentObserver(
                Telephony.CarrierIdentification.CONTENT_URI, false, mContentObserver);
        SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener(
                mOnSubscriptionsChangedListener);
        PreferenceManager.getDefaultSharedPreferences(mContext)
                .registerOnSharedPreferenceChangeListener(mSharedPrefListener);
        UiccController.getInstance().registerForIccChanged(this, ICC_CHANGED_EVENT, null);
    }

    /**
     * Entry point for the carrier identification.
     *
     *    1. SIM_LOAD_EVENT
     *        This indicates that all SIM records has been loaded and its first entry point for the
     *        carrier identification. Note, there are other attributes could be changed on the fly
     *        like APN and SPN. We cached all carrier matching rules based on MCCMNC to speed
     *        up carrier resolution on following trigger events.
     *
     *    2. PREFER_APN_UPDATE_EVENT
     *        This indicates prefer apn has been changed. It could be triggered when user modified
     *        APN settings or when default data connection first establishes on the current carrier.
     *        We follow up on this by querying prefer apn sqlite and re-issue carrier identification
     *        with the updated prefer apn name.
     *
     *    3. SPN_OVERRIDE_EVENT
     *        This indicates that SPN value as been changed. It could be triggered from EF_SPN
     *        record loading, carrier config override
     *        {@link android.telephony.CarrierConfigManager#KEY_CARRIER_NAME_STRING}
     *        or carrier app override {@link TelephonyManager#setOperatorBrandOverride(String)}.
     *        we follow up this by checking the cached mSPN against the latest value and issue
     *        carrier identification only if spn changes.
     *
     *    4. CARRIER_ID_DB_UPDATE_EVENT
     *        This indicates that carrierIdentification database which stores all matching rules
     *        has been updated. It could be triggered from OTA or assets update.
     */
    @Override
    public void handleMessage(Message msg) {
        if (VDBG) logd("handleMessage: " + msg.what);
        switch (msg.what) {
            case SIM_LOAD_EVENT:
            case CARRIER_ID_DB_UPDATE_EVENT:
                mSpn = mTelephonyMgr.getSimOperatorNameForPhone(mPhone.getPhoneId());
                mPreferApn = getPreferApn();
                loadCarrierMatchingRulesOnMccMnc();
                break;
            case SIM_ABSENT_EVENT:
                mCarrierMatchingRulesOnMccMnc.clear();
                mSpn = null;
                mPreferApn = null;
                updateCarrierIdAndName(INVALID_CARRIER_ID, null);
                break;
            case PREFER_APN_UPDATE_EVENT:
                String preferApn = getPreferApn();
                if (!equals(mPreferApn, preferApn, true)) {
                    logd("[updatePreferApn] from:" + mPreferApn + " to:" + preferApn);
                    mPreferApn = preferApn;
                    matchCarrier();
                }
                break;
            case SPN_OVERRIDE_EVENT:
                String spn = mTelephonyMgr.getSimOperatorNameForPhone(mPhone.getPhoneId());
                if (!equals(mSpn, spn, true)) {
                    logd("[updateSpn] from:" + mSpn + " to:" + spn);
                    mSpn = spn;
                    matchCarrier();
                }
                break;
            case ICC_CHANGED_EVENT:
                IccRecords newIccRecords = UiccController.getInstance().getIccRecords(
                        mPhone.getPhoneId(), UiccController.APP_FAM_3GPP);
                if (mIccRecords != newIccRecords) {
                    if (mIccRecords != null) {
                        logd("Removing stale icc objects.");
                        mIccRecords.unregisterForSpnUpdate(this);
                        mIccRecords = null;
                    }
                    if (newIccRecords != null) {
                        logd("new Icc object");
                        newIccRecords.registerForSpnUpdate(this, SPN_OVERRIDE_EVENT, null);
                        mIccRecords = newIccRecords;
                    }
                }
                break;
            default:
                loge("invalid msg: " + msg.what);
                break;
        }
    }

    private void loadCarrierMatchingRulesOnMccMnc() {
        try {
            String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId());
            Cursor cursor = mContext.getContentResolver().query(CarrierIdentification.CONTENT_URI,
                    /* projection */ null,
                    /* selection */ CarrierIdentification.MCCMNC + "=?",
                    /* selectionArgs */ new String[]{mccmnc}, null);
            try {
                if (cursor != null) {
                    if (VDBG) {
                        logd("[loadCarrierMatchingRules]- " + cursor.getCount()
                                + " Records(s) in DB" + " mccmnc: " + mccmnc);
                    }
                    mCarrierMatchingRulesOnMccMnc.clear();
                    while (cursor.moveToNext()) {
                        mCarrierMatchingRulesOnMccMnc.add(makeCarrierMatchingRule(cursor));
                    }
                    matchCarrier();
                }
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
        } catch (Exception ex) {
            loge("[loadCarrierMatchingRules]- ex: " + ex);
        }
    }

    private String getPreferApn() {
        Cursor cursor = mContext.getContentResolver().query(
                Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "preferapn/subId/"
                + mPhone.getSubId()), /* projection */ new String[]{Telephony.Carriers.APN},
                /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null);
        try {
            if (cursor != null) {
                if (VDBG) {
                    logd("[getPreferApn]- " + cursor.getCount() + " Records(s) in DB");
                }
                while (cursor.moveToNext()) {
                    String apn = cursor.getString(cursor.getColumnIndexOrThrow(
                            Telephony.Carriers.APN));
                    logd("[getPreferApn]- " + apn);
                    return apn;
                }
            }
        } catch (Exception ex) {
            loge("[getPreferApn]- exception: " + ex);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return null;
    }

    private void updateCarrierIdAndName(int cid, String name) {
        boolean update = false;
        if (!equals(name, mCarrierName, true)) {
            logd("[updateCarrierName] from:" + mCarrierName + " to:" + name);
            mCarrierName = name;
            update = true;
        }
        if (cid != mCarrierId) {
            logd("[updateCarrierId] from:" + mCarrierId + " to:" + cid);
            mCarrierId = cid;
            update = true;
        }
        if (update) {
            // TODO new public intent CARRIER_ID_CHANGED
            mCarrierIdLocalLog.log("[updateCarrierIdAndName] cid:" + mCarrierId + " name:"
                    + mCarrierName);
        }
    }

    private CarrierMatchingRule makeCarrierMatchingRule(Cursor cursor) {
        return new CarrierMatchingRule(
                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.MCCMNC)),
                cursor.getString(cursor.getColumnIndexOrThrow(
                        CarrierIdentification.IMSI_PREFIX_XPATTERN)),
                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.GID1)),
                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.GID2)),
                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.PLMN)),
                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.SPN)),
                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.APN)),
                cursor.getInt(cursor.getColumnIndexOrThrow(CarrierIdentification.CID)),
                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.NAME)));
    }

    /**
     * carrier matching attributes with corresponding cid
     */
    private static class CarrierMatchingRule {
        /**
         * These scores provide the hierarchical relationship between the attributes, intended to
         * resolve conflicts in a deterministic way. The scores are constructed such that a match
         * from a higher tier will beat any subsequent match which does not match at that tier,
         * so MCCMNC beats everything else. This avoids problems when two (or more) carriers rule
         * matches as the score helps to find the best match uniquely. e.g.,
         * rule 1 {mccmnc, imsi} rule 2 {mccmnc, imsi, gid1} and rule 3 {mccmnc, imsi, gid2} all
         * matches with subscription data. rule 2 wins with the highest matching score.
         */
        private static final int SCORE_MCCMNC          = 1 << 6;
        private static final int SCORE_IMSI_PREFIX     = 1 << 5;
        private static final int SCORE_GID1            = 1 << 4;
        private static final int SCORE_GID2            = 1 << 3;
        private static final int SCORE_PLMN            = 1 << 2;
        private static final int SCORE_SPN             = 1 << 1;
        private static final int SCORE_APN             = 1 << 0;

        private static final int SCORE_INVALID         = -1;

        // carrier matching attributes
        private String mMccMnc;
        private String mImsiPrefixPattern;
        private String mGid1;
        private String mGid2;
        private String mPlmn;
        private String mSpn;
        private String mApn;

        // user-facing carrier name
        private String mName;
        // unique carrier id
        private int mCid;

        private int mScore = 0;

        CarrierMatchingRule(String mccmnc, String imsiPrefixPattern, String gid1, String gid2,
                String plmn, String spn, String apn, int cid, String name) {
            mMccMnc = mccmnc;
            mImsiPrefixPattern = imsiPrefixPattern;
            mGid1 = gid1;
            mGid2 = gid2;
            mPlmn = plmn;
            mSpn = spn;
            mApn = apn;
            mCid = cid;
            mName = name;
        }

        // Calculate matching score. Values which aren't set in the rule are considered "wild".
        // All values in the rule must match in order for the subscription to be considered part of
        // the carrier. otherwise, a invalid score -1 will be assigned. A match from a higher tier
        // will beat any subsequent match which does not match at that tier. When there are multiple
        // matches at the same tier, the longest, best match will be used.
        public void match(CarrierMatchingRule subscriptionRule) {
            mScore = 0;
            if (mMccMnc != null) {
                if (!CarrierIdentifier.equals(subscriptionRule.mMccMnc, mMccMnc, false)) {
                    mScore = SCORE_INVALID;
                    return;
                }
                mScore += SCORE_MCCMNC;
            }
            if (mImsiPrefixPattern != null) {
                if (!imsiPrefixMatch(subscriptionRule.mImsiPrefixPattern, mImsiPrefixPattern)) {
                    mScore = SCORE_INVALID;
                    return;
                }
                mScore += SCORE_IMSI_PREFIX;
            }
            if (mGid1 != null) {
                // full string match. carrier matching should cover the corner case that gid1
                // with garbage tail due to SIM manufacture issues.
                if (!CarrierIdentifier.equals(subscriptionRule.mGid1, mGid1, true)) {
                    mScore = SCORE_INVALID;
                    return;
                }
                mScore += SCORE_GID1;
            }
            if (mGid2 != null) {
                // full string match. carrier matching should cover the corner case that gid2
                // with garbage tail due to SIM manufacture issues.
                if (!CarrierIdentifier.equals(subscriptionRule.mGid2, mGid2, true)) {
                    mScore = SCORE_INVALID;
                    return;
                }
                mScore += SCORE_GID2;
            }
            if (mPlmn != null) {
                if (!CarrierIdentifier.equals(subscriptionRule.mPlmn, mPlmn, true)) {
                    mScore = SCORE_INVALID;
                    return;
                }
                mScore += SCORE_PLMN;
            }
            if (mSpn != null) {
                if (!CarrierIdentifier.equals(subscriptionRule.mSpn, mSpn, true)) {
                    mScore = SCORE_INVALID;
                    return;
                }
                mScore += SCORE_SPN;
            }
            if (mApn != null) {
                if (!CarrierIdentifier.equals(subscriptionRule.mApn, mApn, true)) {
                    mScore = SCORE_INVALID;
                    return;
                }
                mScore += SCORE_APN;
            }
        }

        private boolean imsiPrefixMatch(String imsi, String prefixXPattern) {
            if (TextUtils.isEmpty(prefixXPattern)) return true;
            if (TextUtils.isEmpty(imsi)) return false;
            if (imsi.length() < prefixXPattern.length()) {
                return false;
            }
            for (int i = 0; i < prefixXPattern.length(); i++) {
                if ((prefixXPattern.charAt(i) != 'x') && (prefixXPattern.charAt(i) != 'X')
                        && (prefixXPattern.charAt(i) != imsi.charAt(i))) {
                    return false;
                }
            }
            return true;
        }

        public String toString() {
            return "[CarrierMatchingRule] -"
                    + " mccmnc: " + mMccMnc
                    + " gid1: " + mGid1
                    + " gid2: " + mGid2
                    + " plmn: " + mPlmn
                    + " imsi_prefix: " + mImsiPrefixPattern
                    + " spn: " + mSpn
                    + " apn: " + mApn
                    + " name: " + mName
                    + " cid: " + mCid
                    + " score: " + mScore;
        }
    }

    /**
     * find the best matching carrier from candidates with matched MCCMNC and notify
     * all interested parties on carrier id change.
     */
    private void matchCarrier() {
        if (!SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) {
            logd("[matchCarrier]" + "skip before sim records loaded");
            return;
        }
        final String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId());
        final String gid1 = mPhone.getGroupIdLevel1();
        final String gid2 = mPhone.getGroupIdLevel2();
        final String imsi = mPhone.getSubscriberId();
        final String plmn = mPhone.getPlmn();
        final String spn = mSpn;
        final String apn = mPreferApn;

        if (VDBG) {
            logd("[matchCarrier]"
                    + " gid1: " + gid1
                    + " gid2: " + gid2
                    + " imsi: " + Rlog.pii(LOG_TAG, imsi)
                    + " plmn: " + plmn
                    + " spn: " + spn
                    + " apn: " + apn);
        }

        CarrierMatchingRule subscriptionRule = new CarrierMatchingRule(
                mccmnc, imsi, gid1, gid2, plmn,  spn, apn, INVALID_CARRIER_ID, null);

        int maxScore = CarrierMatchingRule.SCORE_INVALID;
        CarrierMatchingRule maxRule = null;

        for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) {
            rule.match(subscriptionRule);
            if (rule.mScore > maxScore) {
                maxScore = rule.mScore;
                maxRule = rule;
            }
        }
        if (maxScore == CarrierMatchingRule.SCORE_INVALID) {
            logd("[matchCarrier - no match] cid: " + INVALID_CARRIER_ID + " name: " + null);
            updateCarrierIdAndName(INVALID_CARRIER_ID, null);
        } else {
            logd("[matchCarrier] cid: " + maxRule.mCid + " name: " + maxRule.mName);
            updateCarrierIdAndName(maxRule.mCid, maxRule.mName);
        }
    }

    public int getCarrierId() {
        return mCarrierId;
    }

    public String getCarrierName() {
        return mCarrierName;
    }

    private static boolean equals(String a, String b, boolean ignoreCase) {
        if (a == null && b == null) return true;
        if (a != null && b != null) {
            return (ignoreCase) ? a.equalsIgnoreCase(b) : a.equals(b);
        }
        return false;
    }

    private static void logd(String str) {
        Rlog.d(LOG_TAG, str);
    }
    private static void loge(String str) {
        Rlog.e(LOG_TAG, str);
    }
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
        ipw.println("mCarrierIdLocalLogs:");
        ipw.increaseIndent();
        mCarrierIdLocalLog.dump(fd, pw, args);
        ipw.decreaseIndent();

        ipw.println("mCarrierId: " + mCarrierId);
        ipw.println("mCarrierName: " + mCarrierName);

        ipw.println("mCarrierMatchingRules on mccmnc: "
                + mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId()));
        ipw.increaseIndent();
        for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) {
            ipw.println(rule.toString());
        }
        ipw.decreaseIndent();

        ipw.println("mSpn: " + mSpn);
        ipw.println("mPreferApn: " + mPreferApn);
        ipw.flush();
    }
}
+27 −0

File changed.

Preview size limit exceeded, changes collapsed.

+16 −0
Original line number Diff line number Diff line
@@ -2881,6 +2881,13 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
        return null;
    }

    /**
     * Retrieves the EF_PNN from the UICC For GSM/UMTS phones.
     */
    public String getPlmn() {
        return null;
    }

    /**
     * Get the current for the default apn DataState. No change notification
     * exists at this interface -- use
@@ -2986,6 +2993,15 @@ public abstract class Phone extends Handler implements PhoneInternalInterface {
        return;
    }

    public int getCarrierId() {
        // TODO remove hardcoding and expose a public API for INVALID CARRIER ID
        return -1;
    }

    public String getCarrierName() {
        return null;
    }

    /**
     * Return if UT capability of ImsPhone is enabled or not
     */
+4 −0
Original line number Diff line number Diff line
@@ -77,6 +77,10 @@ public class TelephonyComponentFactory {
        return new CarrierActionAgent(phone);
    }

    public CarrierIdentifier makeCarrierIdentifier(Phone phone) {
        return new CarrierIdentifier(phone);
    }

    public IccPhoneBookInterfaceManager makeIccPhoneBookInterfaceManager(Phone phone) {
        return new IccPhoneBookInterfaceManager(phone);
    }
+278 −0

File added.

Preview size limit exceeded, changes collapsed.