工作中会经常遇到PLMN和SPN显示的问题, 这部分和协议关系密切,所以仔细读协议对掌握这部分的知识是很好的; 下面只是将工作中常用的部分做简单总结, 以便快速处理问题。
下图是协议TS 51.011 10.3.11部分对Display condition 的定义。
b1=0:如果registered PLMN是HPLMN或者在Service Provider PLMN List中, 那么registered PLMN是不要求显示的。
b1=1:如果registered PLMN是HPLMN或者在Service Provider PLMN List中, 那么registered PLMN是要求显示的。
b2=0:如果registered PLMN既不是HPLMN也不在Service Provider PLMN List中,那么SPN是要求显示的。
b2=1:如果registered PLMN既不是HPLMN也不在Service Provider PLMN List中,那么SPN是不要求显示的。
*要特别注意“要求显示”的两种情况。
*Service provider PLMN List在EF_SPDI(service provider display information)中, 详情见TS 51.011 10.3.50
在TS 22.101的附录A(A4)中有如下描述:
The service provider name is stored in the USIM in text and/or optionally graphic format. It shall be possible to
associate at least 10 PLMN Identifications (MCC+MNC combination) with the same SP Name.
When registered on the HPLMN, or one of the PLMN Identifications used for Service Provider Name display:
(i) The SP Name shall be displayed;
(ii) Display of the PLMN Name is an operator’s option by setting the appropriate fields in the USIM (i.e. the Service
Provider name shall be displayed either in parallel to the PLMN Name or instead of the PLMN Name).
When registered on neither the HPLMN, nor one of the PLMN Identifications used for Service Provider Name display:
(i) The PLMN name shall be displayed;
(ii) Display of the SP Name is an operator’s option by setting the appropriate fields in the USIM.
If the UE is unable to display the full name of the Service Provider the name is cut from the tail end. The storage of
Service Provider name and options, and choice of options, shall be under control of the network operator.
以Android O源码中SIMRecords.java的getDisplayRule(String plmn)方法作为实例,总结如下:
1.如果operator name被overide, 那么要显示registered PLMN.
2.如果卡里的SPN或者display rule没有获取到,那么显示registered PLMN。
3.如果registered plmn是HPLMN或者在Service provider PLMN List中,b1=1的时候显示registered PLMN, 否则默认显示SPN。
4.如果registered PLMN既不是HPLMN也不在Service Provider PLMN List中, b2=0的时候显示SPN, 否则默认显示registered PLMN。
/**
* Returns the SpnDisplayRule based on settings on the SIM and the
* specified plmn (currently-registered PLMN). See TS 22.101 Annex A
* and TS 51.011 10.3.11 for details.
*
* If the SPN is not found on the SIM or is empty, the rule is
* always PLMN_ONLY.
*/
@Override
public int getDisplayRule(String plmn) {
int rule;
if (mParentApp != null && mParentApp.getUiccCard() != null &&
mParentApp.getUiccCard().getOperatorBrandOverride() != null) {
// If the operator has been overridden, treat it as the SPN file on the SIM did not exist.
rule = SPN_RULE_SHOW_PLMN;
} else if (TextUtils.isEmpty(getServiceProviderName()) || mSpnDisplayCondition == -1) {
// No EF_SPN content was found on the SIM, or not yet loaded. Just show ONS.
rule = SPN_RULE_SHOW_PLMN;
} else if (isOnMatchingPlmn(plmn)) {/*registered plmn是HPLMN或者在Service provider PLMN List中,如果b1=1,那么显示registered PLMN, 否则显示SPN。*/
rule = SPN_RULE_SHOW_SPN;
if ((mSpnDisplayCondition & 0x01) == 0x01) {
// ONS required when registered to HPLMN or PLMN in EF_SPDI
rule |= SPN_RULE_SHOW_PLMN;
}
} else {/*registered plmn既不是HPLMN也不在Service provider PLMN List中; 如果b2=0,那么显示SPN, 否则显示registered PLMN。*/
rule = SPN_RULE_SHOW_PLMN;
if ((mSpnDisplayCondition & 0x02) == 0x00) {
// SPN required if not registered to HPLMN or PLMN in EF_SPDI
rule |= SPN_RULE_SHOW_SPN;
}
}
return rule;
}
registered PLMN(虽然有从SIM里取值的情况,但是应该也是根据网络来取合适的值):
在工作中, 看到了几个平台厂商关于这部分的逻辑,中间还夹杂了OEM厂商的定制需求,虽然各不相同,但大致如下:
1. Eons(Enhanced Operator Name String),从SIM的EF_OPL和EF_PNN来读取Plmn Name。
EF_OPL中存放的是LAC(不是定值,是范围值)和EF_PNN中的Record index。
EF_PNN中存放的是具体的Plmn Name。
如果注册上的网络是HPLMN,那么EF_OPL返回的Record index就是1。
如果不是HPLMN,则根据LAC在EF_OPL中寻找对应的Record index,然后根据Record index,在PNN中找对应的Network Name。
需要注意的是,Record index是基于1的,而EF_PNN的记录是基于0的, 所以根据Record index 从获取EF_PNN记录时要将record index 减去1。
2. CPHS ONS(Common PCN Handset Specification Operator Name String),该字串取值于SIM文件系统的EF_SPN_CPHS或EF_SPN_SHORT_CPHS。
关于CPHS是有一个规范的,奈何没能在协议网找到文件,感兴趣的可以baidu/google。
- NITZ Operator Name
该名称是由当前注册的网络下发给手机的, 平台习惯用property的方式存储。 - 配置文件读取
如果以上的几种途径都没有获取到当前的Plmn Name,那么可能会使用配置好的数据,以xml配置文件的形式或者直接以code形式; 虚拟运营商(MVNO)往往采用这种方式。 - 利用MCCMNC作为Plmn Name
如果以上方式都没能获取有效数据, 那么可以使用mcc+mnc作为PLMN name.
SPN读取:
- 从SIM文件系统读取
可以存储在SIM的EF_SPN(0x6F46)、EF_SPN_CPHS(0x6f14)、EF_SPN_SHORT_CPHS(0x6f18)三个地址上 - 从配置文件读取
SpnOverride 这个类用来从spn配置文件读取相关值。
下面是SIMRecords里面用于读取配置的一个方法。
private void handleCarrierNameOverride() {
CarrierConfigManager configLoader = (CarrierConfigManager)
mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
if (configLoader != null && configLoader.getConfig().getBoolean(
CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL)) {/*读取CarrierConfig里面的配置*/
String carrierName = configLoader.getConfig().getString(
CarrierConfigManager.KEY_CARRIER_NAME_STRING);
setServiceProviderName(carrierName);
mTelephonyManager.setSimOperatorNameForPhone(mParentApp.getPhoneId(),
carrierName);
} else {/*如果CarrierConfig没有将 CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL配置为True, 那么从SpnOverride获取配置文件的配置。*/
setSpnFromConfig(getOperatorNumeric());
}
}
ServiceStateTracker.java里面的updateSpnDisplay方法(只关注GSM部分):
protected void updateSpnDisplay() {
updateOperatorNameFromEri();//CDMA相关
String wfcVoiceSpnFormat = null;
String wfcDataSpnFormat = null;
if (mPhone.getImsPhone() != null && mPhone.getImsPhone().isWifiCallingEnabled()) {/*WiFi Calling相关处理 start*/
// In Wi-Fi Calling mode show SPN+WiFi
String[] wfcSpnFormats = mPhone.getContext().getResources().getStringArray(
com.android.internal.R.array.wfcSpnFormats);
int voiceIdx = 0;
int dataIdx = 0;
CarrierConfigManager configLoader = (CarrierConfigManager)
mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
if (configLoader != null) {
try {
PersistableBundle b = configLoader.getConfigForSubId(mPhone.getSubId());
if (b != null) {
voiceIdx = b.getInt(CarrierConfigManager.KEY_WFC_SPN_FORMAT_IDX_INT);
dataIdx = b.getInt(
CarrierConfigManager.KEY_WFC_DATA_SPN_FORMAT_IDX_INT);
}
} catch (Exception e) {
loge("updateSpnDisplay: carrier config error: " + e);
}
}
wfcVoiceSpnFormat = wfcSpnFormats[voiceIdx];
wfcDataSpnFormat = wfcSpnFormats[dataIdx];
}/*WiFi Calling相关处理 end*/
int combinedRegState = getCombinedRegState();//获取注册状态; 如果voice OOS,就综合考虑data 注册状态。
if (mPhone.isPhoneTypeGsm()) {/*GSM 部分*/
// The values of plmn/showPlmn change in different scenarios./*处理Plmn和showPlmn Start*/
// 1) No service but emergency call allowed -> expected
// to show "Emergency call only"
// EXTRA_SHOW_PLMN = true
// EXTRA_PLMN = "Emergency call only"
// 2) No service at all --> expected to show "No service"
// EXTRA_SHOW_PLMN = true
// EXTRA_PLMN = "No service"
// 3) Normal operation in either home or roaming service
// EXTRA_SHOW_PLMN = depending on IccRecords rule
// EXTRA_PLMN = plmn
// 4) No service due to power off, aka airplane mode
// EXTRA_SHOW_PLMN = false
// EXTRA_PLMN = null
IccRecords iccRecords = mIccRecords;
String plmn = null;
boolean showPlmn = false;
int rule = (iccRecords != null) ? iccRecords.getDisplayRule(mSS.getOperatorNumeric()) : 0;//获取SIM卡里的显示规则。
if (combinedRegState == ServiceState.STATE_OUT_OF_SERVICE
|| combinedRegState == ServiceState.STATE_EMERGENCY_ONLY) {/*OOS/Emergency only的场景,显示PLMN。*/
showPlmn = true;
if (mEmergencyOnly) {---Emergency only
// No service but emergency call allowed
plmn = Resources.getSystem().
getText(com.android.internal.R.string.emergency_calls_only).toString();
} else {---OOS
// No service at all
plmn = Resources.getSystem().
getText(com.android.internal.R.string.lockscreen_carrier_default).toString();
}
if (DBG) log("updateSpnDisplay: radio is on but out " +
"of service, set plmn='" + plmn + "'");
} else if (combinedRegState == ServiceState.STATE_IN_SERVICE) {/*In service状态, 对应注释里的‘3)’。*/
// In either home or roaming service
plmn = mSS.getOperatorAlpha();
showPlmn = !TextUtils.isEmpty(plmn) &&
((rule & SIMRecords.SPN_RULE_SHOW_PLMN)
== SIMRecords.SPN_RULE_SHOW_PLMN);
} else {/*这个场景的处理和注释里面有冲突, 以code为准吧。*/
// Power off state, such as airplane mode, show plmn as "No service"
showPlmn = true;
plmn = Resources.getSystem().
getText(com.android.internal.R.string.lockscreen_carrier_default).toString();
if (DBG) log("updateSpnDisplay: radio is off w/ showPlmn="
+ showPlmn + " plmn=" + plmn);
}/*处理Plmn和showPlmn End*/
// The value of spn/showSpn are same in different scenarios./*处理Spn和showSpn start*/
// EXTRA_SHOW_SPN = depending on IccRecords rule and radio/IMS state
// EXTRA_SPN = spn
// EXTRA_DATA_SPN = dataSpn
String spn = (iccRecords != null) ? iccRecords.getServiceProviderName() : "";//获取SIM卡里面的service provider name信息。
String dataSpn = spn;
boolean showSpn = !TextUtils.isEmpty(spn)
&& ((rule & SIMRecords.SPN_RULE_SHOW_SPN)
== SIMRecords.SPN_RULE_SHOW_SPN);
if (!TextUtils.isEmpty(spn) && !TextUtils.isEmpty(wfcVoiceSpnFormat) &&
!TextUtils.isEmpty(wfcDataSpnFormat)) {/*SPN + WiFi的场景。*/
// In Wi-Fi Calling mode show SPN+WiFi
String originalSpn = spn.trim();
spn = String.format(wfcVoiceSpnFormat, originalSpn);
dataSpn = String.format(wfcDataSpnFormat, originalSpn);
showSpn = true;
showPlmn = false;
} else if (mSS.getVoiceRegState() == ServiceState.STATE_POWER_OFF
|| (showPlmn && TextUtils.equals(spn, plmn))) {/*飞行模式,或者spn和plmn相同时不显示spn。*/
// airplane mode or spn equals plmn, do not show spn
spn = null;
showSpn = false;
}/*处理Spn和showSpn End*/
int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
int[] subIds = SubscriptionManager.getSubId(mPhone.getPhoneId());
if (subIds != null && subIds.length > 0) {
subId = subIds[0];
}
// Update SPN_STRINGS_UPDATED_ACTION IFF any value changes
if (mSubId != subId || /*卡的信息发生变化, 需要更新spn。*/
showPlmn != mCurShowPlmn
|| showSpn != mCurShowSpn
|| !TextUtils.equals(spn, mCurSpn)
|| !TextUtils.equals(dataSpn, mCurDataSpn)
|| !TextUtils.equals(plmn, mCurPlmn)) {
if (DBG) {
log(String.format("updateSpnDisplay: changed sending intent rule=" + rule +
" showPlmn='%b' plmn='%s' showSpn='%b' spn='%s' dataSpn='%s' " +
"subId='%d'", showPlmn, plmn, showSpn, spn, dataSpn, subId));
}
Intent intent = new Intent(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);//发送广播通知spn更新,MobileSignalController会处理这个广播,用于更新状态栏里面的spn显示。
intent.putExtra(TelephonyIntents.EXTRA_SHOW_SPN, showSpn);
intent.putExtra(TelephonyIntents.EXTRA_SPN, spn);
intent.putExtra(TelephonyIntents.EXTRA_DATA_SPN, dataSpn);
intent.putExtra(TelephonyIntents.EXTRA_SHOW_PLMN, showPlmn);
intent.putExtra(TelephonyIntents.EXTRA_PLMN, plmn);
SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());
mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);
if (!mSubscriptionController.setPlmnSpn(mPhone.getPhoneId(),
showPlmn, plmn, showSpn, spn)) {/*SubscriptionController.setPlmnSpn方法更新数据库siminfo表中的carrier_name字段; 另外调用notifySubscriptionInfoChanged通知监听者*/
mSpnUpdatePending = true;
}
}
mSubId = subId;
mCurShowSpn = showSpn;
mCurShowPlmn = showPlmn;
mCurSpn = spn;
mCurDataSpn = dataSpn;
mCurPlmn = plmn;
} else {
/*...CDMA部分*/
}