PLMN & SPN

工作中会经常遇到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。
这里写图片描述

  1. NITZ Operator Name
    该名称是由当前注册的网络下发给手机的, 平台习惯用property的方式存储。
  2. 配置文件读取
    如果以上的几种途径都没有获取到当前的Plmn Name,那么可能会使用配置好的数据,以xml配置文件的形式或者直接以code形式; 虚拟运营商(MVNO)往往采用这种方式。
  3. 利用MCCMNC作为Plmn Name
    如果以上方式都没能获取有效数据, 那么可以使用mcc+mnc作为PLMN name.
SPN读取:
  1. 从SIM文件系统读取
    可以存储在SIM的EF_SPN(0x6F46)、EF_SPN_CPHS(0x6f14)、EF_SPN_SHORT_CPHS(0x6f18)三个地址上
  2. 从配置文件读取
    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部分*/
    }
### SPNPLMN的定义及区别 SPN(Service Provider Name)是指服务提供商名称,通常是由SIM卡提供的一段文本信息,用于标识当前使用的移动网络运营商。该名称可以直接从SIM卡中读取,并且在网络注册时作为显示用途[^2]。 PLMN(Public Land Mobile Network)则是公共陆地移动网络的缩写,它是一个由国际移动用户识别码(IMSI)中的前五位数字所决定的唯一标识符,用来表示一个特定的移动通信网络。PLMN代码由MCC(Mobile Country Code,移动国家码)MNC(Mobile Network Code,移动网络代码)组成,其中MCC确定了国家或地区,而MNC则进一步指定了具体的运营商[^1]。 ### 显示条件及其逻辑 在某些情况下,设备会根据注册到的PLMN是否属于HPLMN(Home PLMN,即用户的归属PLMN)或者是否出现在Service Provider PLMN List上来决定是否需要显示PLMN名称。当b1位设置为0时,如果注册的是HPLMN或者是Service Provider PLMN列表中的某个条目,则不需要显示PLMN;若b1设为1,则即使满足上述条件也必须显示PLMN。对于那些既不是HPLMN也不在Service Provider PLMN List里的PLMN来说,若b2位为0,则要求显示SPN;反之,当b2设为1时,则不强制显示SPN[^1]。 Android系统处理这些名称显示的方式是通过ServiceStateTracker组件来实现的,它负责获取并确定应该展示哪种类型的网络名称——无论是PLMN还是SPN亦或是两者结合的形式。这一过程涉及到对SIM卡内存储的信息进行解析以确认最终的显示规则[^2]。 ### 实现细节与定制化 MTK平台提供了lookupOperatorName函数来动态更新运营商名称的显示内容,允许开发者自定义优先级顺序。例如,在SA模式下的5G网络环境中,可以配置让设备优先使用来自EONS(Enhanced Operator Name String)的数据源来进行名称展示。此外,还可以调用getEonsIfExist方法从SIM卡中提取相关的EF文件信息作为参考依据之一[^3]。 ```python def determine_display_name(registered_plmn, hplmn, spn_list, b1, b2): """ 根据给定参数判断应显示哪个名称 :param registered_plmn: 当前注册的PLMN :param hplmn: 用户归属PLMN :param spn_list: Service Provider PLMN列表 :param b1: 控制HPLMNSPN列表内PLMN是否需显示 :param b2: 控制非HPLMN且不在SPN列表内的PLMN是否需显示SPN :return: 应显示的名字类型 ('PLMN', 'SPN' 或者 '') """ if registered_plmn == hplmn or registered_plmn in spn_list: # 检查b1状态 return 'PLMN' if b1 else '' else: # 检查b2状态 return 'SPN' if not b2 else '' # 示例使用 registered_plmn = "46000" # 假设的已注册PLMN hplmn = "46001" # 假设的HPLMN spn_list = ["46000", "46002"] # 假定的服务提供商PLMN列表 b1 = 1 # 设定b1值 b2 = 0 # 设定b2值 display_name = determine_display_name(registered_plmn, hplmn, spn_list, b1, b2) print(f"根据设定条件,应当显示: {display_name}") ``` 这段示例代码模拟了一个简单的逻辑流程,用于基于提供的参数确定何时显示PLMNSPN名称。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值