功能介绍
根据设备的网络连接情况更新状态栏显示的运营商及网络状态。
注册上WFC(WiFi Calling)后,支持客制化显示左上角状态栏中的运营商网络状态信息 。具体的代码逻辑在CarrierDisplayNameResolver.java。
ServiceStateTracker 网络状态变化触发更新,流程如下:
- updateSpnDisplay(),有以下场景会更新SPN:
- onSubscriptionsChanged() 注册状态变化时
- BroadcastReceiver() 收到广播
- Intent.ACTION_LOCALE_CHANGED
- TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED
- handleMessage() 收到消息处理
- EVENT_ICC_CHANGED
- EVENT_NITZ_TIME
- EVENT_IMS_CAPABILITY_CHANGED
- EVENT_RUIM_RECORDS_LOADED
- setImsRegistrationState(final boolean registered)——It's possible ServiceState changes did not trigger SPN display update; we update it here
- pollStateDone()——Trigger updateSpnDisplay when 1. Service state is changed. 2. phone type is Cdma or CdmaLte and ERI text has changed.
- updateSpnDisplayCdnr()
- getCarrierDisplayNameData()
- resolveCarrierDisplayName()
- getCarrierDisplayNameFromWifiCallingOverride()
- 获取com.android.internal.R.array.wfcSpnFormats值
相关类:/frameworks/opt/telephony/src/java/com/android/internal/telephony/
- /cdnr/CarrierDisplayNameResolver.java(可客制化抽出来做telephony-common.jar)
- ServiceStateTracker.java
- /cdnr/CarrierDisplayNameData.java(序列化对象)
//frameworks/opt/telephony/src/java/com/android/internal/telephony/ServiceStateTracker.java
//1. SS 变化更新SPN显示
public void updateSpnDisplay() {
if (mCarrierConfig.getBoolean(
CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL)) {
updateSpnDisplayCdnr();
} else {
updateSpnDisplayLegacy();
}
}
//2.目前基本都用cdnr处理方式
private void updateSpnDisplayCdnr() {
log("updateSpnDisplayCdnr+");
CarrierDisplayNameData data = mCdnr.getCarrierDisplayNameData();
notifySpnDisplayUpdate(data);
log("updateSpnDisplayCdnr-");
}
//frameworks/opt/telephony/src/java/com/android/internal/telephony/cdnr/CarrierDisplayNameResolver.java
/** Carrier display name resolver. */
public class CarrierDisplayNameResolver {
private static final boolean DBG = true;
private static final String TAG = "CDNR";
private CarrierDisplayNameData mCarrierDisplayNameData;
//定义SPN来源的优先级,索引小的优先级高。
/**
* The priority of ef source. Lower index means higher priority.
*/
private static final List<Integer> EF_SOURCE_PRIORITY =
Arrays.asList(
EF_SOURCE_CARRIER_API,
EF_SOURCE_CARRIER_CONFIG,
EF_SOURCE_ERI,
EF_SOURCE_USIM,
EF_SOURCE_SIM,
EF_SOURCE_CSIM,
EF_SOURCE_RUIM,
EF_SOURCE_VOICE_OPERATOR_SIGNALLING,
EF_SOURCE_DATA_OPERATOR_SIGNALLING,
EF_SOURCE_MODEM_CONFIG);
//构造方法,根据Phone对象初始化
public CarrierDisplayNameResolver(GsmCdmaPhone phone) {
mLocalLog = new LocalLog(32);
mContext = phone.getContext();
mPhone = phone;
mCCManager = (CarrierConfigManager) mContext.getSystemService(
Context.CARRIER_CONFIG_SERVICE);
}
//3. 获取解析的运营商名称
/** Get the resolved carrier display name. */
public CarrierDisplayNameData getCarrierDisplayNameData() {
resolveCarrierDisplayName();
return mCarrierDisplayNameData;
}
//4.解析运营商名称
private void resolveCarrierDisplayName() {
//(1)从EF文件信息获取
CarrierDisplayNameData data = getCarrierDisplayNameFromEf();
if (DBG) Rlog.d(TAG, "CarrierName from EF: " + data);
//(2)Cross-SIM Calling,目前似乎基本没用到这类
if ((mPhone.getImsPhone() != null) && (mPhone.getImsPhone().getImsRegistrationTech()
== ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM)) {
data = getCarrierDisplayNameFromCrossSimCallingOverride(data);
if (DBG) {
Rlog.d(TAG, "CarrierName override by Cross-SIM Calling " + data);
}
//(3)WFC(国外常用)
} else if (mPhone.getServiceStateTracker().getCombinedRegState(getServiceState())
== ServiceState.STATE_IN_SERVICE) {
//WFC启用并注册上ims
if (mPhone.isWifiCallingEnabled() && mPhone.isImsRegistered()) {
data = getCarrierDisplayNameFromWifiCallingOverride(data);
if (DBG) {
Rlog.d(TAG, "CarrierName override by wifi-calling " + data);
}
} else if (getServiceState().getState() == ServiceState.STATE_POWER_OFF) {
// data in service due to IWLAN but APM on and WFC not available
data = getOutOfServiceDisplayName(data);
if (DBG) Rlog.d(TAG, "Out of service carrierName (APM) " + data);
}
} else {
data = getOutOfServiceDisplayName(data);
if (DBG) Rlog.d(TAG, "Out of service carrierName " + data);
}
if (!Objects.equals(mCarrierDisplayNameData, data)) {
mLocalLog.log(String.format("ResolveCarrierDisplayName: %s", data.toString()));
}
//全局变量唯一赋值的地方。
mCarrierDisplayNameData = data;
}
//从卡EF文件获取运营商显示名称
private CarrierDisplayNameData getCarrierDisplayNameFromEf() {
CarrierDisplayNameConditionRule displayRule = getDisplayRule();
String registeredPlmnName = getServiceState().getOperatorAlpha();
String registeredPlmnNumeric = getServiceState().getOperatorNumeric();
String spn = getEfSpn();
// Resolve the PLMN network name
List<OperatorPlmnInfo> efOpl = getEfOpl();
List<PlmnNetworkName> efPnn = getEfPnn();
String plmn = null;
if (isRoaming()) {
plmn = registeredPlmnName;
} else {
if (efOpl.isEmpty()) {
// If the EF_OPL is not present, then the first record in EF_PNN is used for the
// default network name when registered in the HPLMN or an EHPLMN(if the EHPLMN
// list is present).
plmn = efPnn.isEmpty() ? "" : getPlmnNetworkName(efPnn.get(0));
} else {
// TODO: Check the TAC/LAC & registered PLMN numeric in OPL list to determine which
// PLMN name should be used to override the current one.
}
}
// If no PLMN override is present, then the PLMN should be displayed:
// - operator alpha if it's not empty.
// - operator numeric.
if (TextUtils.isEmpty(plmn)) {
plmn = TextUtils.isEmpty(registeredPlmnName) ? registeredPlmnNumeric
: registeredPlmnName;
}
boolean showSpn = displayRule.shouldShowSpn(spn);
boolean showPlmn = TextUtils.isEmpty(spn) || displayRule.shouldShowPlmn(plmn);
return new CarrierDisplayNameData.Builder()
.setSpn(spn)
.setShowSpn(showSpn)
.setPlmn(plmn)
.setShowPlmn(showPlmn)
.build();
}
//5.获取WFC场景运营商名称
private CarrierDisplayNameData getCarrierDisplayNameFromWifiCallingOverride(
CarrierDisplayNameData rawCarrierDisplayNameData) {
PersistableBundle config = getCarrierConfig();
boolean useRootLocale = config.getBoolean(CarrierConfigManager.KEY_WFC_SPN_USE_ROOT_LOCALE);
Context displayNameContext = mContext;
if (useRootLocale) {
Configuration displayNameConfig = mContext.getResources().getConfiguration();
displayNameConfig.setLocale(Locale.ROOT);
// Create a new Context for this temporary change
displayNameContext = mContext.createConfigurationContext(displayNameConfig);
}
Resources r = displayNameContext.getResources();
String[] wfcSpnFormats = r.getStringArray(com.android.internal.R.array.wfcSpnFormats);
WfcCarrierNameFormatter wfcFormatter = new WfcCarrierNameFormatter(config, wfcSpnFormats,
getServiceState().getState() == ServiceState.STATE_POWER_OFF);
// Override the spn, data spn, plmn by wifi-calling
String wfcSpn = wfcFormatter.formatVoiceName(rawCarrierDisplayNameData.getSpn());
String wfcDataSpn = wfcFormatter.formatDataName(rawCarrierDisplayNameData.getSpn());
List<PlmnNetworkName> efPnn = getEfPnn();
String plmn = efPnn.isEmpty() ? "" : getPlmnNetworkName(efPnn.get(0));
String wfcPlmn = wfcFormatter.formatVoiceName(
TextUtils.isEmpty(plmn) ? rawCarrierDisplayNameData.getPlmn() : plmn);
CarrierDisplayNameData result = rawCarrierDisplayNameData;
if (!TextUtils.isEmpty(wfcSpn) && !TextUtils.isEmpty(wfcDataSpn)) {
result = new CarrierDisplayNameData.Builder()
.setSpn(wfcSpn)
.setDataSpn(wfcDataSpn)
.setShowSpn(true)
.build();
} else if (!TextUtils.isEmpty(wfcPlmn)) {
result = new CarrierDisplayNameData.Builder()
.setPlmn(wfcPlmn)
.setShowPlmn(true)
.build();
}
return result;
}
变量与配置
CarrierConfig 相关变量,关联wfcSpnFormats
value | Type | Comment and Eg |
---|---|---|
wfc_spn_format_idx_int | int | WiFi Calling 的 SPN 格式索引值。 <int name="wfc_spn_format_idx_int" value="1"/> |
wfc_data_spn_format_idx_int | int | 数据连接的 WiFi Calling 的 SPN 格式索引值 <int name="wfc_data_spn_format_idx_int" value="0" /> |
wfc_flight_mode_spn_format_idx_int | int | 飞行模式下使用的 WiFi Calling 的 SPN 格式索引值 <int name="wfc_flight_mode_spn_format_idx_int" value="0" /> |
具体定义见CarrierConfigManager.java注释说明:
//frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java
// wfc_spn_format_idx_int 表示 WiFi Calling 的 SPN 格式索引值。
// %s 表示插入运营商名称的占位符。
/**
* Indexes of SPN format strings in wfcSpnFormats.
*
* <p>Available options are:
* <ul>
* <li> 0: %s</li>
* <li> 1: %s Wi-Fi Calling</li>
* <li> 2: WLAN Call</li>
* <li> 3: %s WLAN Call</li>
* <li> 4: %s Wi-Fi</li>
* <li> 5: WiFi Calling | %s</li>
* <li> 6: %s VoWifi</li>
* <li> 7: Wi-Fi Calling</li>
* <li> 8: Wi-Fi</li>
* <li> 9: WiFi Calling</li>
* <li> 10: VoWifi</li>
* <li> 11: %s WiFi Calling</li>
* @hide
*/
public static final String KEY_WFC_SPN_FORMAT_IDX_INT = "wfc_spn_format_idx_int";
/**
* Indexes of data SPN format strings in wfcSpnFormats.
*
* @see KEY_WFC_SPN_FORMAT_IDX_INT for available options.
* @hide
*/
public static final String KEY_WFC_DATA_SPN_FORMAT_IDX_INT = "wfc_data_spn_format_idx_int";
/**
* Indexes of SPN format strings in wfcSpnFormats used during flight mode.
*
* Set to -1 to use the value from KEY_WFC_SPN_FORMAT_IDX_INT also in this case.
* @see KEY_WFC_SPN_FORMAT_IDX_INT for other available options.
* @hide
*/
public static final String KEY_WFC_FLIGHT_MODE_SPN_FORMAT_IDX_INT =
"wfc_flight_mode_spn_format_idx_int";
代码路径
- packages/apps/CarrierConfig 原生默认机制
- device/mediatek/system/common(mtk原生overlay资源,要看分区,device/mediatek/common也类似)
- spn-conf.xml
- overlay/telephony/frameworks/base/core/res/res的value文件 ,如values-mcc302-mnc610,针对运营商mccmnc定制
模块Module | 完整路径 | 配置文件名 | 功能 | 优先级 |
---|---|---|---|---|
CarrierConfig | packages/apps/CarrierConfig | carrier_name影响SPN显示 | ||
device SPN | device/mediatek/system/common device/mediatek/common | spn-conf.xml | SPN配置 | |
device overlay | overlay/telephony/frameworks/base/core/res/res | values-mcc505-mnc03/strings.xml | WFC SPN | |
fw/base | /frameworks/base/core/res/res/(Android U参考) | 如values-mcc302-mnc370-ko/strings.xml <string-array name="wfcSpnFormats"> | WFC SPN |
配置案例
overlay方式配置
overlay/common/frameworks/base/core/res/res/values-mcc505-mnc03/strings.xml
<?xml version="1.0" encoding="UTF-8"?>
<resources xmlns:xmliff="urn:oasis:names:tc:xliff:document:1.2">
<string-array name="wfcSpnFormats">
<item>"Vodafone"</item>
</string-array>
</resources>
spn-conf.xml
原生机制,源码有该文件和运营商配置
http://androidxref.com/9.0.0_r3/xref/device/google/cuttlefish/shared/config/spn-conf.xml
常见问题和疑问
问题:data连接后SPN显示运营商名字正常,但是飞行模式下WFC注册上左上角状态栏还是显示“airplane mode”。
默认wfc spn没有配置会显示什么?
APM时WFCSPN没有配置会用默认的KEY_WFC_SPN_FORMAT_IDX_INT,而这个值又默认是0啊,为什么状态栏还是显示飞行模式呢??
/frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java
//frameworks/base/telephony/java/android/telephony/CarrierConfigManager.java
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
static {
sDefaults = new PersistableBundle();
//飞行模式默认是-1,那就是会显示飞行模式啊
sDefaults.putInt(KEY_WFC_SPN_FORMAT_IDX_INT, 0);
sDefaults.putInt(KEY_WFC_DATA_SPN_FORMAT_IDX_INT, 0);
sDefaults.putInt(KEY_WFC_FLIGHT_MODE_SPN_FORMAT_IDX_INT, -1);
}
ServiceState 测试代码参考
//frameworks/opt/telephony/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@Before
public void setUp() throws Exception {
super.setUp("ServiceStateTrackerTest");
mContextFixture.putStringArrayResource(
com.android.internal.R.array.wfcSpnFormats,
WIFI_CALLING_FORMATTERS);
mBundle.putBoolean(
CarrierConfigManager.KEY_ENABLE_CARRIER_DISPLAY_NAME_RESOLVER_BOOL, true);
//创建的时候赋值,但是CarrierConfigManager的默认值不同。
mBundle.putInt(CarrierConfigManager.KEY_WFC_SPN_FORMAT_IDX_INT, 0);
mBundle.putInt(CarrierConfigManager.KEY_WFC_DATA_SPN_FORMAT_IDX_INT, 1);
mBundle.putInt(CarrierConfigManager.KEY_WFC_FLIGHT_MODE_SPN_FORMAT_IDX_INT, 2);
}
Log关键字/日志分析
- isVowifiEnabled=| WfcCarrierName 正则表达式过滤
- WFC下更新SPN: CarrierName override by wifi-calling