常用的获取方式

手机上运营商名称的显示,通常包括状态栏显示,锁屏界面显示以及设置下面部分菜单的显示,最常用的获取方式如下:

Sting operatorName = mSubscriptionInfo.getDisplayName();

其中mSubInfoRecord为SubscriptionInfo,获取的是SubscriptionInfo中的mDisplayName,而SubscriptionInfo的获取为以下两种方式:

final List<SubscriptionInfo> slist = mSubscriptionManager.getActiveSubscriptionInfoList();
final List<SubscriptionInfo> subInfoList = SubscriptionManager.from(context).getActiveSubscriptionInfoList();

运营商名称的初始化

但是对于SubscriptionInfo中的mDisplayName,他的初始化可分为两种:

方式一:创建SubscriptionInfo对象的时候直接进行赋值:
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
       CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
       Bitmap icon, int mcc, int mnc, String countryIso, boolean isEmbedded,
       @Nullable UiccAccessRule[] accessRules) {
   this.mId = id;
   this.mIccId = iccId;
   this.mSimSlotIndex = simSlotIndex;
   this.mDisplayName = displayName;
   this.mCarrierName = carrierName;
   this.mNameSource = nameSource;
   this.mIconTint = iconTint;
   this.mNumber = number;
   this.mDataRoaming = roaming;
   this.mIconBitmap = icon;
   this.mMcc = mcc;
   this.mMnc = mnc;
   this.mCountryIso = countryIso;
   this.mIsEmbedded = isEmbedded;
   this.mAccessRules = accessRules;
}
方式二:创建setDisplayName(CharSequence name)进行赋值:
public void setDisplayName(CharSequence name) {
    this.mDisplayName = name;
}

根据经验,通常系统SubscriptionManager都是会早早启动,但是运营商名称信息的话需要等识别到SIM卡相关信息之后才能更新,这就使得系统必须通过方式二去更新运营商名称。根据猜想于是抓取了开机log,从log信息中看到了以下:

12-03 13:21:46.427435   990  1180 D MtkSubscriptionInfoUpdater: [handleSimLoaded]- simNumeric: 46000, simMvnoName: CMCC
12-03 13:21:46.427435   990  1180 D MtkSubscriptionInfoUpdater: [handleSimLoaded]- simNumeric: 46000, simMvnoName: CMCC

查找handleSimLoaded信息,最中定位MtkSubscriptionInfoUpdater.java中进行的操作:

protected void handleSimLoaded(int slotId) {
    ......
    if (isAllIccIdQueryDone()) {
            if (needUpdate) {
                updateSubscriptionInfoByIccId();
            }
            // MTK-END
            int[] subIds = mSubscriptionManager.getActiveSubscriptionIdList();
            for (int subId : subIds) {
                ......
                SubscriptionInfo subInfo = mSubscriptionManager.getActiveSubscriptionInfo(subId);
                String nameToSet;
                String simCarrierName = tm.getSimOperatorName(subId);
                ContentValues name = new ContentValues(1);

                if (subInfo != null && subInfo.getNameSource() !=
                        SubscriptionManager.NAME_SOURCE_USER_INPUT) {
                    // MTK-START
                    // Take MVNO into account.
                    String simNumeric = tm.getSimOperatorNumeric(subId);
                    String simMvnoName = MtkSpnOverride.getInstance().lookupOperatorNameForDisplayName(
                            subId, simNumeric, true, mContext);
                    //此处有获取运营商名称的操作
                    logd("[handleSimLoaded]- simNumeric: " + simNumeric +
                                ", simMvnoName: " + simMvnoName);
                    if (!TextUtils.isEmpty(simMvnoName)) {
                        nameToSet = simMvnoName;
                    } else {
                        if (!TextUtils.isEmpty(simCarrierName)) {
                            nameToSet = simCarrierName;
                        } else {
                            nameToSet = "CARD " + Integer.toString(slotId + 1);
                        }
                    }
                    // MTK-END
                    // MTK-START
                    //name.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
                    //logd("sim name = " + nameToSet);
                    //contentResolver.update(SubscriptionManager.CONTENT_URI, name,
                    //        SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID
                    //        + "=" + Long.toString(subId), null);

                    // refresh Cached Active Subscription Info List
                    SubscriptionController.getInstance()
                            .refreshCachedActiveSubscriptionInfoList();
                    mSubscriptionManager.setDisplayName(nameToSet, subId);
                    //此处通过SubscriptionManager的setDisplayName设置运营商名称;
                    //怀疑最终是设置到了SubscriptionInfo对象当中;
                    logd("[handleSimLoaded] subId = " + subId + ", sim name = " + nameToSet);
                    // MTK-END
                }
                ......
            }
        }
}

既然有怀疑对象,接着往下查看,重点对SubscriptionManager的setDisplayName方法进行分析:

public int setDisplayName(String displayName, int subId, long nameSource) {
    if (VDBG) {
        logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId
                + " nameSource:" + nameSource);
    }
    if (!isValidSubscriptionId(subId)) {
        logd("[setDisplayName]- fail");
        return -1;
    }

    int result = 0;

    try {
        ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
        if (iSub != null) {
            result = iSub.setDisplayNameUsingSrc(displayName, subId, nameSource);
        }
    } catch (RemoteException ex) {
        // ignore it
    }

    return result;

}

继续往下查看setDisplayNameUsingSrc(String displayName, int subId, long nameSource)方法发现有做以下三件事情:
1.更新数据库的SIM卡信息;
2.通过refreshCachedActiveSubscriptionInfoList()更新Cache中的SubscriptionInfo信息;
3.通过notifySubscriptionInfoChanged()更新一些其他信息(待探索);
而SubscriptionInfo的信息的更新极大概率只会出现在2,3操作当中。

public int setDisplayNameUsingSrc(String displayName, int subId, long nameSource) {
    if (DBG) {
        logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId
            + " nameSource:" + nameSource);
    }

    enforceModifyPhoneState("setDisplayNameUsingSrc");

    // Now that all security checks passes, perform the operation as ourselves.
    final long identity = Binder.clearCallingIdentity();
    try {
        validateSubId(subId);
        String nameToSet;
        if (displayName == null) {
            nameToSet = mContext.getString(SubscriptionManager.DEFAULT_NAME_RES);
        } else {
            nameToSet = displayName;
        }
        ContentValues value = new ContentValues(1);
        value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
        if (nameSource >= SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE) {
            if (DBG) logd("Set nameSource=" + nameSource);
            value.put(SubscriptionManager.NAME_SOURCE, nameSource);
        }
        if (DBG) logd("[setDisplayName]- mDisplayName:" + nameToSet + " set");
        // TODO(b/33075886): If this is an embedded subscription, we must also save the new name
        // to the eSIM itself. Currently it will be blown away the next time the subscription
        // list is updated.

        int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
                value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
                Long.toString(subId), null);

        // Refresh the Cache of Active Subscription Info List
        refreshCachedActiveSubscriptionInfoList();

        notifySubscriptionInfoChanged();

        return result;
    } finally {
        Binder.restoreCallingIdentity(identity);
    }
}

个人首先怀疑点是notifySubscriptionInfoChanged()去做的更新,但是通过查找相关代码,最终未能发现该方法之后有做任何关于SubscriptionInfo更新的操作,此路不通,需要重新寻找方法。

于是回到起点,SubscriptionInfo是通过SubscriptionManager.getActiveSubscriptionInfoList()获取到的一个集合中的数据,所以我们看找getActiveSubscriptionInfoList()方法的具体获取:

public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
    List<SubscriptionInfo> result = null;

    try {
        ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
        if (iSub != null) {
            result = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName());
        }
    } catch (RemoteException ex) {
        // ignore it
    }
    return result;
}

这个地方只是AIDL调用,还得找到实际操作的地方,最终找到SubscriptionController.java文件中的getActiveSubscriptionInfoList()的方法:

@Override
public List<SubscriptionInfo> getActiveSubscriptionInfoList(String callingPackage) {

    if (!canReadPhoneState(callingPackage, "getActiveSubscriptionInfoList")) {
        return null;
    }

    // Now that all security checks passes, perform the operation as ourselves.
    final long identity = Binder.clearCallingIdentity();
    try {
        if (!isSubInfoReady()) {
            if (DBG) logdl("[getActiveSubInfoList] Sub Controller not ready");
            return null;
        }

        // Get the active subscription info list from the cache if the cache is not null
        List<SubscriptionInfo> tmpCachedSubList = mCacheActiveSubInfoList.get();
        //关键代码,最终的获取是从mCacheActiveSubInfoList获取的。
        if (tmpCachedSubList != null) {
            if (DBG_CACHE) {
                for (SubscriptionInfo si : tmpCachedSubList) {
                    logd("[getActiveSubscriptionInfoList] Getting Cached subInfo=" + si);
                }
            }
            return new ArrayList<SubscriptionInfo>(tmpCachedSubList);
        } else {
            if (DBG_CACHE) {
                logd("[getActiveSubscriptionInfoList] Cached subInfo is null");
            }
            return null;
        }
    } finally {
        Binder.restoreCallingIdentity(identity);
    }
}

到此终于看到了数据的获取地点,最终的SubscriptionInfo数据是从mCacheActiveSubInfoList中获取出来的,再回去看SubscriptionController.java中的setDisplayNameUsingSrc(String displayName, int subId, long nameSource)中也包含更新Cache数据的操作,是否两者有联系?
于是查找refreshCachedActiveSubscriptionInfoList()方法:

public /*protected*/ void refreshCachedActiveSubscriptionInfoList() {

    // Now that all security checks passes, perform the operation as ourselves.
    final long identity = Binder.clearCallingIdentity();
    try {
        if (!isSubInfoReady()) {
            if (DBG_CACHE) {
                logdl("[refreshCachedActiveSubscriptionInfoList] "
                        + "Sub Controller not ready ");
            }
            return;
        }

        List<SubscriptionInfo> subList = getSubInfo(
                SubscriptionManager.SIM_SLOT_INDEX + ">=0", null);

        if (subList != null) {
            // FIXME: Unnecessary when an insertion sort is used!
            subList.sort(SUBSCRIPTION_INFO_COMPARATOR);

            if (DBG_CACHE) {
                logdl("[refreshCachedActiveSubscriptionInfoList]- " + subList.size()
                        + " infos return");
            }
        } else {
            if (DBG_CACHE) logdl("[refreshCachedActiveSubscriptionInfoList]- no info return");
        }

        if (DBG_CACHE) {
            for (SubscriptionInfo si : subList) {
                logd("[refreshCachedActiveSubscriptionInfoList] Setting Cached subInfo=" + si);
            }
        }
        mCacheActiveSubInfoList.set(subList);
        //更新mCacheActiveSubInfoList中的数据

    } finally {
        Binder.restoreCallingIdentity(identity);
    }
}

查找mCacheActiveSubInfoList对应的get和set方法如下,从方法可以看出,SubscriptionController通过set将数据更新到mCacheActiveSubInfoList中,其他地方需要使用时通过get方法从mCacheActiveSubInfoList获取出来。

/**
 * Gets the current value.
 *
 * @return the current value
 */
public final V get() {
    return value;
}

/**
 * Sets to the given value.
 *
 * @param newValue the new value
 */
public final void set(V newValue) {
    value = newValue;
}

至此,SubscriptionInfo数据的获取和更新有了完成的流程!
此时再回到mSubscriptionManager.setDisplayName(nameToSet, subId)中,我们此时需要关注的是nameToSet是怎么来的?

运营商名称的获取

既然mDisplayName是从handleSimLoaded(int slotId)方法中通过mSubscriptionManager.setDisplayName(nameToSet, subId)更新,那nameToSet参数的获取同样要从此方法中寻找:

// MTK-START
// Take MVNO into account.
String simNumeric = tm.getSimOperatorNumeric(subId);
String simMvnoName = MtkSpnOverride.getInstance().lookupOperatorNameForDisplayName(
        subId, simNumeric, true, mContext);
logd("[handleSimLoaded]- simNumeric: " + simNumeric +
            ", simMvnoName: " + simMvnoName);
if (!TextUtils.isEmpty(simMvnoName)) {
    nameToSet = simMvnoName;
} else {
    if (!TextUtils.isEmpty(simCarrierName)) {
        nameToSet = simCarrierName;
    } else {
        nameToSet = "CARD " + Integer.toString(slotId + 1);
    }
}
// MTK-END

顺序为:simMvnoName,simNumeric(网络下发的运营商名称),CARD+(slotId + 1),基本上不会出现CARD+(slotId + 1)的情况。具体最需要看的是simMvnoName。接着往下看:

public String lookupOperatorName(int subId, String numeric,
        boolean desireLongName, Context context, String defaultName) {
    String operName = null;

    // Step 1. check if phone is available.
    Phone phone = PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
    if (phone == null) {
        Rlog.w(LOG_TAG_EX, "lookupOperatorName getPhone null");
        return defaultName;
    }

    // Step 2. get spn from mvno parrtern.
    operName = getSpnByPattern(subId, numeric);

    // Step 3. check if need special handling (CT only).
    boolean getCtSpn = isForceGetCtSpnFromRes(subId, numeric, context, operName);

    // Step 4. get Spn by numeric from resource and spn_conf.xml
    if (operName == null || getCtSpn) {
        operName = getSpnByNumeric(numeric, desireLongName, context, getCtSpn, true);
    }

    // Step5. if didn't found any spn, return default name.
    return ((operName == null) ? defaultName : operName);
}

/*
 * Use to query SIM operator name for MVNO feature with default name numeric.
 *
 */
public String lookupOperatorName(int subId, String numeric,
        boolean desireLongName, Context context) {
    return lookupOperatorName(subId, numeric, desireLongName, context, numeric);
}

/*
 * Use to query SIM operator name for MVNO feature with default empty name.
 *
 */
public String lookupOperatorNameForDisplayName(int subId,
        String numeric, boolean desireLongName, Context context) {
    return lookupOperatorName(subId, numeric, desireLongName, context, null);
}

从以上方法来看,simMvnoName的获取分五个步骤,具体如下:
1.首先确认Phone是否可用,不可用的话直接返回defaultName,其实就是null;
2.检查虚拟运营商信息;
3.判断是否是CT测试手机;
4.特殊运营商名称单独获取,之后再会去spn_conf.xml文件中查找;
5.返回查找的数据;
而我们最终关注的是虚拟运营商信息的检查部分,什么是虚拟运营商?

没有自己的实体网络,通过租用正常运营商的网络来提供网络服务。虚拟运营商的SIM卡与正常运营商的SIM的区别是:虚拟运营商会在SIM卡中的某支文件中定义某个栏位,以表示自己是虚拟运营商,根据增加栏位的文件不同,分为以下几种情况(MTK):

  1. EF_SPN方式,对应MVNO配置到Virtual-spn-conf-by-efspn.xml中
  2. EF_IMSI方式,对应MVNO配置到Virtual-spn-conf-by-imsi.xml中
  3. EF_PNN方式,对应MVNO配置到Virtual-spn-conf-by-efpnn.xml中
  4. EF_GID1方式,对应MVNO配置到Virtual-spn-conf-by-efgid1.xml中

具体的代码如下:

public String getSpnByPattern(int subId, String numeric) {
    Phone phone = PhoneFactory.getPhone(SubscriptionManager.getPhoneId(subId));
    String mvnoOperName = null;

    mvnoOperName = getSpnByEfSpn(numeric,
             ((MtkGsmCdmaPhone)phone).getMvnoPattern(MtkPhoneConstants.MVNO_TYPE_SPN));
    Rlog.d(LOG_TAG_EX, "the result of searching mvnoOperName by EF_SPN: " + mvnoOperName);

    if (mvnoOperName == null) {
        mvnoOperName = getSpnByImsi(numeric, phone.getSubscriberId());
        Rlog.d(LOG_TAG_EX, "the result of searching mvnoOperName by IMSI: " + mvnoOperName);
    }

    if (mvnoOperName == null) {
        mvnoOperName = getSpnByEfPnn(numeric,
                ((MtkGsmCdmaPhone)phone).getMvnoPattern(MtkPhoneConstants.MVNO_TYPE_PNN));
        Rlog.d(LOG_TAG_EX, "the result of searching mvnoOperName by EF_PNN: " + mvnoOperName);
    }

    if (mvnoOperName == null) {
        mvnoOperName = getSpnByEfGid1(numeric,
                ((MtkGsmCdmaPhone)phone).getMvnoPattern(MtkPhoneConstants.MVNO_TYPE_GID));
        Rlog.d(LOG_TAG_EX, "the result of searching mvnoOperName by EF_GID1: " + mvnoOperName);
    }

        return mvnoOperName;
    }

至于虚拟运营商的spn文件如何配置,可能每个芯片厂商设置的不一样,MTK的设置如下:
(1)通过EF_SPN区分
这中方式是读取SIM中的文件EF_SPN,结合SIM的mccmnc+spn,在virtual-spn-conf-by-efspn.xml 中查找有没有对应的记录,如果有这表示这个SIM是MVNO的卡,同时取name字段的内容当作运营商名称。
如果知道MVNO的SIM卡中的SPN是“abc”,MNO的MCC/MNC是10000,期望显示运营商名是”MVNO“,那就这样加记录(在Virtual-spn-conf-by-efspn.xml中)

<virtualSpnOverride mccmncspn="10000abc“ name="MVNO">

相应的,apn中要添加mvno属性,以表示是虚拟运营商的参数:

mvno_type="spn" 
mvno_match_data="abc"

(2)通过EF_IMSI区分

这中方式是imsi中有一段特殊的数字标识用于和MNO区分,例如MNO的MCC/MNC是46692,MVNO的IMSI是466923302848289,IMSI的第9位起连续2个数字为特殊标识(28),期望显示的运营商名称是“MVNO”,那就这样加记录(Virtual-spn-conf-by-imsi.xml中)

<virtualSpnOverride imsipattern="4669246692×××28×××××" name=“MVNO”>

相应的,apn中要添加mvno属性,以表示是虚拟运营商的参数:

mvno_type="imsi"
mvno_match_data="46692×××28×××××"

(3)通过EF_PNN区分
EF_PNN是SIM中的一个option的文件,里面存放一组网络运营商名称(PLMN Network Name)。这种方式即是读取EF_PNN中的第一个pnn来匹配。如果MNO的MCC/MNC是10000,MVNO中EF_PNN的第一个pnn是“abc”,期望显示的运营商名称是“MVNO”,那就这样加记录(Virtual-spn-conf-by-efpnn.xml中)

<virtualSpnOverride mccmncpnn="10000abc“ name="MVNO">

相应的,apn中要添加mvno属性,以表示是虚拟运营商的参数:

mvno_type="pnn"   
mvno_match_data="abc"

(4)通过EF_GID1区分

EF_GID1是SIM中的一个option的文件,里面存放了n个byte的数据;如果MNO的MCC/MNC是10000,MVNO的EF_GID1的内容是”11”,期望显示的运营商名称是”MVNO”,那就这样加记录(Virtual-spn-conf-by-efgid1.xml中)

<virtualSpnOverride mccmncgid1="1000011" name="MVNO">

相应的,apn中要添加mvno属性,以表示是虚拟运营商的参数:

mvno_type="gid"
mvno_match_data="11"