一、功能名词简介和显示规则
Alpha Tag:运营商名称标识符,也是用于标识运营商的一个名称。客户需求描述常用名词,对开发而言都是SPN/PLMN功能模块的内容,状态栏左上角的运营商名称显示。
SPN相关文章:
【笔记】SPN和PLMN 运营商网络名称显示_spn plmn-CSDN博客
Android U 配置 WiFiCalling 场景下PLMN/SPN 显示的代码逻辑介绍
网络运营商名称显示规则:
MTK平台的设计,对运营商名称的显示rule 是基于sim相关协议来实现的。优先级是Eons>nitz> XML配置(spn-con.xml)。EONS具有最高优先级,如果拿不到的EONS的情况下,要去读NITZ里的name。(Reference :FAQ08919)
- EONS (Enhanced Operator Name String,“增强型运营商名称字符串”):是在 GSM 网络中引入的一种机制,用于向移动设备发送关于当前所处位置和网络状态的更详细和更准确的信息,以便于移动设备更好地显示和呈现运营商信息。在传统的 GSM 网络中,运营商名称(即 SPN)通常只包含运营商的品牌名称或简称,例如“China Mobile”或“AT&T”。这种信息的显示可能无法反映出当前所处的具体位置或网络状态,例如是否在漫游状态、网络类型、是否处于特殊服务状态等。为了解决这个问题,EONS 引入了更多的信息,以便于移动设备能够更好地显示和呈现运营商信息。
- NITZ (Network Identity and Time Zone,“网络识别码和时区”):它是一种用于向移动设备发送网络识别码和时区信息的协议,通常在移动设备启动时或网络状态发生变化时进行同步更新。
二、代码逻辑
基于 Android T&U 版本分析。
(一)T和U代码差异
两个版本有差异,主要是Android U 删除了 SubscriptionController类,新增SubscriptionManagerService类。在Android T代码中也有备注setPlmnSpn接口适用maxSDK是R,有过渡提示。
- T:SubscriptionController.java setPlmnSpn
- U:SubscriptionManagerService.java getCarrierName
在ServiceStateTracker.java中,T上同事更新plmn,但U上已经不再更新SPN,也没有setPlmnSpn接口。
(二)【Android T】setPlmnSpn
onSubscriptionsChanged() => setPlmnSpn() => setCarrierText() =>refreshCachedActiveSubscriptionInfoList() & notifySubscriptionInfoChanged()
Android T 更新SPN代码流程:
- onSubscriptionsChanged()
- setPlmnSpn()
- setCarrierText()
- refreshCachedActiveSubscriptionInfoList() & notifySubscriptionInfoChanged()
ServiceStateTracker.java
frameworks/opt/telephony/src/java/com/android/internal/telephony/ServiceStateTracker.java
在网络状态变化时SST内部类SstSubscriptionsChangedListener监听收到onSubscriptionsChanged() 回调,去更新PLMN和SPN。
onSubscriptionsChanged()
private class SstSubscriptionsChangedListener extends OnSubscriptionsChangedListener {/*** Callback invoked when there is any change to any SubscriptionInfo. Typically* this method would invoke {@link SubscriptionManager#getActiveSubscriptionInfoList}*/@Overridepublic void onSubscriptionsChanged() {if (DBG) log("SubscriptionListener.onSubscriptionInfoChanged");final int curSubId = mPhone.getSubId();// If the sub info changed, but the subId is the same, then we're done.if (mSubId == curSubId) return;// If not, then the subId has changed, so we need to remember the old subId,// even if the new subId is invalid (likely).mPrevSubId = mSubId;mSubId = curSubId;mPhone.notifyPhoneStateChanged();setDataNetworkTypeForPhone(mSS.getRilDataRadioTechnology());//setPlmnSpn 更新PLMN/SPNif (mSpnUpdatePending) {mSubscriptionController.setPlmnSpn(mPhone.getPhoneId(), mCurShowPlmn,mCurPlmn, mCurShowSpn, mCurSpn);mSpnUpdatePending = false;}//更新相关Settings设置内容。// Remove old network selection sharedPreferences since SP key names are now// changed to include subId. This will be done only once when upgrading from an// older build that did not include subId in the names.SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);String oldNetworkSelection = sp.getString(Phone.NETWORK_SELECTION_KEY, "");String oldNetworkSelectionName = sp.getString(Phone.NETWORK_SELECTION_NAME_KEY, "");String oldNetworkSelectionShort = sp.getString(Phone.NETWORK_SELECTION_SHORT_KEY, "");if (!TextUtils.isEmpty(oldNetworkSelection)|| !TextUtils.isEmpty(oldNetworkSelectionName)|| !TextUtils.isEmpty(oldNetworkSelectionShort)) {SharedPreferences.Editor editor = sp.edit();editor.putString(Phone.NETWORK_SELECTION_KEY + mSubId,oldNetworkSelection);editor.putString(Phone.NETWORK_SELECTION_NAME_KEY + mSubId,oldNetworkSelectionName);editor.putString(Phone.NETWORK_SELECTION_SHORT_KEY + mSubId,oldNetworkSelectionShort);editor.remove(Phone.NETWORK_SELECTION_KEY);editor.remove(Phone.NETWORK_SELECTION_NAME_KEY);editor.remove(Phone.NETWORK_SELECTION_SHORT_KEY);editor.commit();}// Once sub id becomes valid, we need to update the service provider name// displayed on the UI again. The old SPN update intents sent to// MobileSignalController earlier were actually ignored due to invalid sub id.updateSpnDisplay();}};
SubscriptionController.java
/*** Generate and set carrier text based on input parameters* @param showPlmn flag to indicate if plmn should be included in carrier text* @param plmn plmn to be included in carrier text* @param showSpn flag to indicate if spn should be included in carrier text* @param spn spn to be included in carrier text* @return true if carrier text is set, false otherwise*/@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)public boolean setPlmnSpn(int slotIndex, boolean showPlmn, String plmn, boolean showSpn,String spn) {synchronized (mLock) {int subId = getSubIdUsingPhoneId(slotIndex);if (mContext.getPackageManager().resolveContentProvider(SubscriptionManager.CONTENT_URI.getAuthority(), 0) == null ||!SubscriptionManager.isValidSubscriptionId(subId)) {// No place to store this info. Notify registrants of the change anyway as they// might retrieve the SPN/PLMN text from the SST sticky broadcast.// TODO: This can be removed once SubscriptionController is not running on devices// that don't need it, such as TVs.if (DBG) logd("[setPlmnSpn] No valid subscription to store info");notifySubscriptionInfoChanged();return false;}String carrierText = "";if (showPlmn) {carrierText = plmn;if (showSpn) {//当PLMN和SPN不相同时,就会显示PLMN-SPN// Need to show both plmn and spn if both are not same.if(!Objects.equals(spn, plmn)) {String separator = mContext.getString(com.android.internal.R.string.kg_text_message_separator).toString();carrierText = new StringBuilder().append(carrierText).append(separator).append(spn).toString();}}} else if (showSpn) {carrierText = spn;}setCarrierText(carrierText, subId);return true;}}/*** Set carrier text by simInfo index* @param text new carrier text* @param subId the unique SubInfoRecord index in database* @return the number of records updated*/private int setCarrierText(String text, int subId) {if (DBG) logd("[setCarrierText]+ text:" + text + " subId:" + subId);enforceModifyPhoneState("setCarrierText");// Now that all security checks passes, perform the operation as ourselves.final long identity = Binder.clearCallingIdentity();try {boolean update = true;int result = 0;SubscriptionInfo subInfo = getSubscriptionInfo(subId);if (subInfo != null) {update = !TextUtils.equals(text, subInfo.getCarrierName());}if (update) {ContentValues value = new ContentValues(1);value.put(SubscriptionManager.CARRIER_NAME, text);result = mContext.getContentResolver().update(SubscriptionManager.getUriForSubscriptionId(subId), value, null, null);// Refresh the Cache of Active Subscription Info ListrefreshCachedActiveSubscriptionInfoList();notifySubscriptionInfoChanged();} else {if (DBG) logd("[setCarrierText]: no value update");}return result;} finally {Binder.restoreCallingIdentity(identity);}}
(三) 【Android U】SetCarrierName
pollStateDone() 等 => updateSpnDisplay() => updateSpnDisplayCdnr() => notifySpnDisplayUpdate() => mSubscriptionManagerService.setCarrierName => mSubscriptionDatabaseManager.setCarrierName(subId, carrierName);
Android U 更新SPN代码流程:
- updateSpnDisplay()
- updateSpnDisplayCdnr()
- notifySpnDisplayUpdate()——先获取spn再set
- setCarrierName() 从manager到database
ServiceStateTracker.java
/frameworks/opt/telephony/src/java/com/android/internal/telephony/ServiceStateTracker.java
private void notifySpnDisplayUpdate(CarrierDisplayNameData data) {int subId = mPhone.getSubId();// Update ACTION_SERVICE_PROVIDERS_UPDATED if any value changesif (mSubId != subId|| data.shouldShowPlmn() != mCurShowPlmn|| data.shouldShowSpn() != mCurShowSpn|| !TextUtils.equals(data.getSpn(), mCurSpn)|| !TextUtils.equals(data.getDataSpn(), mCurDataSpn)|| !TextUtils.equals(data.getPlmn(), mCurPlmn)) {final String log = String.format("updateSpnDisplay: changed sending intent, "+ "rule=%d, showPlmn='%b', plmn='%s', showSpn='%b', spn='%s', "+ "dataSpn='%s', subId='%d'",getCarrierNameDisplayBitmask(mSS),data.shouldShowPlmn(),data.getPlmn(),data.shouldShowSpn(),data.getSpn(),data.getDataSpn(),subId);mCdnrLogs.log(log);if (DBG) log("updateSpnDisplay: " + log);Intent intent = new Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED);intent.putExtra(TelephonyManager.EXTRA_SHOW_SPN, data.shouldShowSpn());intent.putExtra(TelephonyManager.EXTRA_SPN, data.getSpn());intent.putExtra(TelephonyManager.EXTRA_DATA_SPN, data.getDataSpn());intent.putExtra(TelephonyManager.EXTRA_SHOW_PLMN, data.shouldShowPlmn());intent.putExtra(TelephonyManager.EXTRA_PLMN, data.getPlmn());SubscriptionManager.putPhoneIdAndSubIdExtra(intent, mPhone.getPhoneId());mPhone.getContext().sendStickyBroadcastAsUser(intent, UserHandle.ALL);if (SubscriptionManager.isValidSubscriptionId(subId)) {mSubscriptionManagerService.setCarrierName(subId, TextUtils.emptyIfNull(getCarrierName(data.shouldShowPlmn(), data.getPlmn(),data.shouldShowSpn(), data.getSpn())));}}mCurShowSpn = data.shouldShowSpn();mCurShowPlmn = data.shouldShowPlmn();mCurSpn = data.getSpn();mCurDataSpn = data.getDataSpn();mCurPlmn = data.getPlmn();}@NonNullprivate String getCarrierName(boolean showPlmn, String plmn, boolean showSpn, String spn) {String carrierName = "";if (showPlmn) {carrierName = plmn;if (showSpn) {// Need to show both plmn and spn if both are not same.if (!Objects.equals(spn, plmn)) {String separator = mPhone.getContext().getString(com.android.internal.R.string.kg_text_message_separator).toString();carrierName = new StringBuilder().append(carrierName).append(separator).append(spn).toString();}}} else if (showSpn) {carrierName = spn;}return carrierName;}private void updateSpnDisplayCdnr() {log("updateSpnDisplayCdnr+");CarrierDisplayNameData data = mCdnr.getCarrierDisplayNameData();notifySpnDisplayUpdate(data);log("updateSpnDisplayCdnr-");}@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)@VisibleForTestingpublic void updateSpnDisplay() {if (mCarrierConfig.getBoolean(CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL)) {updateSpnDisplayCdnr();} else {updateSpnDisplayLegacy();}}
会触发SPN更新的场景(即调用updateSpnDisplay)
- BroadcastReceiveronReceive()
- Intent.ACTION_LOCALE_CHANGED
- TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED
- handleMessage()
- EVENT_ICC_CHANGED
- EVENT_SIM_RECORDS_LOADED
- EVENT_IMS_CAPABILITY_CHANGED
- EVENT_RUIM_RECORDS_LOADED
- setImsRegistrationState()
- pollStateDone()
三、开发方案
- T上只用subscriptions状态变化的时候会通过setPlmnSpn(其showPlmn和showSpn的逻辑再U上的getCarrierName接口中)更新名称内容,因此可以在设置spn的入口定制。
- U上SST中包含很多carriername更新的场景,都是在notifySpnDisplayUpdate生效SPN更新 ,而此接口中都是通过get获取后再set设置信的,因此可以在getCarrierName获取的时候再定制内容。
(一)Android T 定制在 setPlmnSpn
Android T 上,可以在 SubscriptionController.java 中修改setPlmnSpn()接口内部逻辑,定制CarrierText(最终显示的字符串内容)。
/*** Generate and set carrier text based on input parameters* @param showPlmn flag to indicate if plmn should be included in carrier text* @param plmn plmn to be included in carrier text* @param showSpn flag to indicate if spn should be included in carrier text* @param spn spn to be included in carrier text* @return true if carrier text is set, false otherwise*/@UnsupportedAppUsagepublic boolean setPlmnSpn(int slotIndex, boolean showPlmn, String plmn, boolean showSpn,String spn) {synchronized (mLock) {int subId = getSubIdUsingPhoneId(slotIndex);//原生逻辑 if (showPlmn) {//...}//在最终更新运营商字符串之前实现定制carrierText = customizeCarrierText(carrierText, subId);setCarrierText(carrierText, subId); //原生逻辑return true;}}//客制化private String customizeCarrierText(String carrierText, int subId) {String customizeCarrierText= carrierText;TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);if (tm != NULL) {String simNumeric = tm.getSimOperatorNumeric(subId); //卡本身的mccmnc,区别于漫游的网络final ServiceState serviceState = tm.getServiceState();boolean isRoaming = (serviceState != null) ? serviceState.getRoaming() : false;//根据卡和网络状态定制举例if(serviceState != null && "46001".equals(simNumeric) && isInService(serviceState)){customizeCarrierText= "CCC";}}return customizeCarrierText;}
(二)Android U 定制在 getCarrierName
Android U上,可在获取SPN后定制内容,
private void notifySpnDisplayUpdate(CarrierDisplayNameData data) {int subId = mPhone.getSubId();// Update ACTION_SERVICE_PROVIDERS_UPDATED if any value changesif (SubscriptionManager.isValidSubscriptionId(subId)) {mSubscriptionManagerService.setCarrierName(subId, TextUtils.emptyIfNull(getCarrierName(data.shouldShowPlmn(), data.getPlmn(),data.shouldShowSpn(), data.getSpn())));}}@NonNullprivate String getCarrierName(boolean showPlmn, String plmn, boolean showSpn, String spn) {String carrierName = "";if (showPlmn) {carrierName = plmn;if (showSpn) {// Need to show both plmn and spn if both are not same.if (!Objects.equals(spn, plmn)) {String separator = mPhone.getContext().getString(com.android.internal.R.string.kg_text_message_separator).toString();carrierName = new StringBuilder().append(carrierName).append(separator).append(spn).toString();}}} else if (showSpn) {carrierName = spn;}//定制carrierName = customizeCarrierText(carrierName, mPhone.getSubId());return carrierName;}private String customizeCarrierText(String carrierText, int subId) {String customizedCarrierText = carrierText;TelephonyManager tm = (TelephonyManager) mPhone.getContext().getSystemService(Context.TELEPHONY_SERVICE);tm = tm.createForSubscriptionId(subId); //优化双卡逻辑if (tm != null && !tm.isWifiCallingAvailable()) { //不更新WFC场景int phoneId = mPhone.getPhoneId();String simNumeric = tm.getSimOperatorNumeric(subId); //卡MCCMNCfinal ServiceState serviceState = tm.getServiceState();String operatorNumeric = (serviceState != null) ? serviceState.getOperatorNumeric() : null; //注册网络的MCCMNC,如果是漫游,会跟simNumeric不同String gid1 = tm.getGroupIdLevel1(subId);//Avoid NullPointerExceptionboolean isRoaming = serviceState == null ? false : serviceState.getRoaming();Log.d("customizeCarrierText: operatorNumeric:" + operatorNumeric + ", simNumeric:" + simNumeric);if (TextUtils.isEmpty(simNumeric)) {simNumeric = mPhone.getOperatorNumeric();}if("46000".equals(operatorNumeric) && "310260".equals(simNumeric)) {//定制期望显示customizedCarrierText = "Visible";}Log.d("customizeCarrierText: simNumeric = " + simNumeric + ", operatorNumeric = " + operatorNumeric + ", customizedCarrierText = " + customizedCarrierText);}return customizedCarrierText;}