常用的获取方式
手机上运营商名称的显示,通常包括状态栏显示,锁屏界面显示以及设置下面部分菜单的显示,最常用的获取方式如下:
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):
- EF_SPN方式,对应MVNO配置到Virtual-spn-conf-by-efspn.xml中
- EF_IMSI方式,对应MVNO配置到Virtual-spn-conf-by-imsi.xml中
- EF_PNN方式,对应MVNO配置到Virtual-spn-conf-by-efpnn.xml中
- 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"