Android U 配置 WiFiCalling 场景下PLMN/SPN 显示的代码逻辑介绍

功能介绍

根据设备的网络连接情况更新状态栏显示的运营商及网络状态。

注册上WFC(WiFi Calling)后,支持客制化显示左上角状态栏中的运营商网络状态信息 。具体的代码逻辑在CarrierDisplayNameResolver.java。

ServiceStateTracker 网络状态变化触发更新,流程如下:

  1. updateSpnDisplay(),有以下场景会更新SPN:
    1. onSubscriptionsChanged() 注册状态变化时
    2. BroadcastReceiver() 收到广播
      1. Intent.ACTION_LOCALE_CHANGED
      2. TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED
    3. handleMessage() 收到消息处理
      1. EVENT_ICC_CHANGED
      2. EVENT_NITZ_TIME
      3. EVENT_IMS_CAPABILITY_CHANGED
      4. EVENT_RUIM_RECORDS_LOADED
    4. setImsRegistrationState(final boolean registered)——It's possible ServiceState changes did not trigger SPN display update; we update it here
    5. pollStateDone()——Trigger updateSpnDisplay when 1. Service state is changed. 2. phone type is Cdma or CdmaLte and ERI text has changed.
  2. updateSpnDisplayCdnr()
  3. getCarrierDisplayNameData()
  4. resolveCarrierDisplayName()
  5. getCarrierDisplayNameFromWifiCallingOverride()
    1. 获取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

TypeComment and Eg
wfc_spn_format_idx_intint

WiFi Calling 的 SPN 格式索引值。

<int name="wfc_spn_format_idx_int" value="1"/>

wfc_data_spn_format_idx_intint

数据连接的 WiFi Calling 的 SPN 格式索引值

<int name="wfc_data_spn_format_idx_int" value="0" />

wfc_flight_mode_spn_format_idx_intint

飞行模式下使用的 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完整路径配置文件名功能优先级
CarrierConfigpackages/apps/CarrierConfigcarrier_name影响SPN显示
device SPN

device/mediatek/system/common

device/mediatek/common

spn-conf.xmlSPN配置
device overlayoverlay/telephony/frameworks/base/core/res/resvalues-mcc505-mnc03/strings.xmlWFC 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 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/346985.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C# Bitmap类学习1

Bitmap对象封装了GDI中的一个位图&#xff0c;此位图由图形图像及其属性的像素数据组成.因此Bitmap是用于处理由像素数据定义的图像的对象。 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using …

程序媛的mac修炼手册-- 如何用Python节省WPS会员费

上篇分享了如何用微博爬虫&#xff0c;咱举例爬了女明星江疏影的微博数据。今天就用这些数据&#xff0c;给大家安利一下怎么用Python实现WPS中部分Excel付费功能。 MacOS系统自带的工具&#xff0c;绝大多数都非常顶&#xff0c;除Numbers外。当然&#xff0c;page比起word来&…

图像分类】【深度学习】【轻量级网络】【Pytorch版本】EfficientNet_V2模型算法详解

【图像分类】【深度学习】【轻量级网络】【Pytorch版本】EfficientNet_V2模型算法详解 文章目录 【图像分类】【深度学习】【轻量级网络】【Pytorch版本】EfficientNet_V2模型算法详解前言EfficientNet_V2讲解自适应正则化的渐进学习(Progressive Learning with adaptive Regul…

魔众题库系统v9.3.0版本:升级与新功能亮点,让学习更高效!

大家好&#xff01;今天我们激动地向大家宣布&#xff0c;魔众题库系统已经升级到了v9.3.0版本&#xff01;这个版本带来了许多令人兴奋的改进和新功能&#xff0c;让用户的使用体验更上一层楼。 首先&#xff0c;让我们来看看这个版本的VIP界面升级。无论是PC端还是移动端&am…

C/C++ - 编程语法特性

目录 标准控制台框架 输入输出对象 命名空间 标准控制台框架 头文件 ​#include <iostream>​​ 告诉编译器我们要使用iostream库尖括号中的名字指定了某个头文件(header) 入口函数 ​int main(void)​​ 返回 ​return 0;​​ 输出语句 ​std::cout << "H…

python基础——锁

进程锁 (互斥锁) 进程锁的引入&#xff1a; 模拟抢票程序&#xff1a; from multiprocessing import Process import json import time def show_ticket(i):with open("./tickets.txt",mode"r",encoding"utf-8") as file:ticket json.load(f…

k8s图形化管理工具之rancher

前言 在前面的k8s基础学习中,我们学习了各种资源的搭配运用,以及命令行,声明式文件创建。这些都是为了k8s管理员体会k8s的框架,内容基础。在真正的生产环境中,大部分的公司还是会选用图形化管理工具来管理k8s集群,大大提高工作效率。 在二进制搭建k8集群时,我们就知道了…

Spring依赖注入之setter注入与构造器注入以及applicationContext.xml配置文件特殊值处理

依赖注入之setter注入 在管理bean对象的组件的时候同时给他赋值&#xff0c;就是setter注入&#xff0c;通过setter注入&#xff0c;可以将某些依赖项标记为可选的&#xff0c;因为它们不是在构造对象时立即需要的。这种方式可以减少构造函数的参数数量&#xff0c;使得类的构…

程序员的自我修养:链接、装载与库 6 可执行文件的装载与进程

1 进程虚拟地址空间 PAE 2 装载的方式 2.1 覆盖装入 省略 178 2.2 页映射 3 从操作系统角度看可执行文件的装载 3.1 进程的建立 182

【必剪】鬼畜rap和鬼畜剧场的区别?

在【选择素材】中&#xff0c;每个素材下会有一个标签显示支持哪种的鬼畜形式&#xff0c;在点击一个两种格式的有【鬼畜剧场】和【鬼畜rap】这两中的主要区别在于 【鬼畜剧场】&#xff1a;对素材进行人工编排&#xff0c;创作自己原创的剧情作 【鬼畜rap】&#xff1a;对于素…

IO多路复用-poll(附通信代码)

IO多路复用-poll 1. poll函数 和select函数的比较 内核对应文件描述符的检测也是以线性的方式进行轮询&#xff0c;根据描述符的状态进行处理poll和select检测的文件描述符集合会在检测过程中频繁的进行用户区和内核区的拷贝&#xff0c;它的开销随着文件描述符数量的增加而…

恒峰配网行波型故障预警定位装置特点及优势

随着电力系统的不断发展&#xff0c;电网运行的安全性和稳定性对于国家经济和人民生活至关重要。为了提高电网运行的可靠性&#xff0c;减少故障发生的可能性&#xff0c;我国电力行业不断引进新技术、新设备&#xff0c;其中配网行波型故障预警定位装置在电网安全领域发挥着越…

CentOS安装Redis教程-shell脚本一键安装配置

文章目录 前言一、Redis单机版安装教程1. 复制脚本2. 增加执行权限3. 执行脚本 二、Redis扩展集群版安装教程1. 安装Redis单机版2. 复制脚本3. 增加执行权限4. 执行脚本5. 测试6. redis_cluster.sh 命令6.1 启动Redis扩展集群6.2 停止Redis扩展集群6.3 查看Redis扩展集群节点信…

mysql 基础(三)

一、多表设计 数据库设计范式 第一范式(确保每列保持原子性) 第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值&#xff0c;就说明该数据库表满足了第一范式。第二范式就是要有主键&#xff0c;要求其他字段都依赖于主键。 没有主键就没有唯一性&…

知识产权实缴注册资金的流程

随着新《公司法》的出台&#xff0c;很多企业老板几乎睡不着&#xff0c;都在为实缴注册资本苦恼。前文有谈到目前比较靠谱的实缴方式是知识产权实缴。那么知识产权实缴的流程是怎么样的&#xff1f;需要准备哪些资料&#xff1f; 下面用一张图为各位企业老板们解读知识产权实…

ios上架缺少info.plist文件

app打包ios提示修改info.plist文件&#xff0c;在iOS原生开发中提供了配置 Info.plist 和 资源文件&#xff08;Bundle Resources&#xff09;。uni-app中对常用项进行了封装&#xff0c;提供了manifest.json。 但manifest.json不能包含所有iOS的配置。需要自定义一个Info.plis…

掌握大语言模型技术: 推理优化

掌握大语言模型技术_推理优化 堆叠 Transformer 层来创建大型模型可以带来更好的准确性、少样本学习能力&#xff0c;甚至在各种语言任务上具有接近人类的涌现能力。 这些基础模型的训练成本很高&#xff0c;并且在推理过程中可能会占用大量内存和计算资源&#xff08;经常性成…

【本科生机器学习】【北京航空航天大学】课题报告:支持向量机(Support Vector Machine, SVM)初步研究【上、原理部分】

说明&#xff1a; &#xff08;1&#xff09;、仅供个人学习使用&#xff1b; &#xff08;2&#xff09;、本科生学术水平有限&#xff0c;故不能保证全无科学性错误&#xff0c;本文仅作为该领域的学习参考。 一、课程总结 1、机器学习&#xff08;Machine Learning, ML&am…

【9.DAC数模转换器】蓝桥杯嵌入式一周拿奖速成系列

系列文章目录 蓝桥杯嵌入式系列文章目录(更多此系列文章可见) DAC数模转换器 系列文章目录一、STM32CUBEMX配置二、项目代码1.main.c --> DACProcess 总结 一、STM32CUBEMX配置 STM32CUBEMX PA4 -> DAC1_OUT1 ; PA5 -> DAC1_OUT2DACProcess 二、项目代码 1.main.c -…

司铭宇老师:销售人员心态激励培训:销售心态调整与情绪压力管理

销售人员心态激励培训&#xff1a;销售心态调整与情绪压力管理&#xff1a;迈向成功的关键要素 导语&#xff1a;在竞争激烈的销售行业中&#xff0c;心态调整与情绪压力管理成为销售人员至关重要的能力。如何在这场博弈中保持良好的心态&#xff0c;有效应对压力&#xff0c;…