一、CarrierConfig简介
CarrierConfig(运营商配置)有一些项目会叫CarrierSettings,首次出现是在Android 6.0 版本中,此机制的出现就是为运营商配置定制的功能。让我们可以弃用静态配置叠加层,通过使用配置文件和指定接口向相应平台动态提供运营商配置。可配置的模块很多,如,漫游/非漫游网络、可视语音信箱、短信/彩信网络设置、VoLTE/即时通讯配置等。
CarrierConfig是一个内置的系统应用:
CarrierConfig和Settings、CellBroadcastReceiver一样,都在packages的apps中都属于系统应用。
一般情况下,我们都是根据"运营商id编号"或者"MCC+MNC"去修改指定的配置文件来设置运营商的默认功能配置,在后面的流程中会详细讲到,我会将CarrierConfig的增删查改方式详细的写下来。
二、CarrierConfig加载流程(增)
CarrierConfig涉及到的类(有些芯片厂商会重写这些类):
与SIM卡初始化流程一样,CarrierConfig的初始化流程也是Phone进程启动过程中的一个支线。
都在在PhoneGlobal.Java的onCreate()方法中被执行:
这个PhoneFactory的makeDefaultPhones()方法就是SIM卡初始化流程开始,CarrierConfig的加载就在SIM卡加载之后。
SIM卡初始化完成后,SIM卡Carrier信息被写到数据库中,但这些数据是可以被CarrierConfig覆盖掉的。
1、CarrierConfigLoader初始化
这个类负责绑定到特权运营商应用程序以获取运营商配置覆盖。是单例模式的,一般我们调用init方法创建对象。当收到服务器启动完成的广播和包发生改变的广播时,开始处理监听器事件。内部实现了一个处理器ConfigHandler。
从注释可以看出,处理器主要负责:从运营商应用或默认应用读取文件信息、清除配置、处理连接并绑定服务成功或IPC错误的后续响应。
1)CarrierConfigLoader构造器
构造了一个CarrierConfigLoader对象并注册一个广播接收相关事件。这个广播用来监听系统的启动、配置文件包内容的改变。
并且将本类注册为服务,我们在其他类可以通过ServiceManager来访问carrierConfig:
在构造器最后还往自己的handler处理器丢了一个消息,清除掉之前缓存的配置。(因为我们的CarrierConfig配置会一直缓存在/data/user_de/0/com.android.phone/files文件夹中,所以,每次重新初始化要清除掉之前缓存文件)
2)ConfigLoaderBroadcastReceiver广播
这个广播在实现在CarrierConfigLoader.java内部
当收到系统启动完成的广播时,也就是系统第一次启动,会触发EVENT_SYSTEM_UNLOCKED事件,通知ConfigHandler执行读取和缓存CarrierConfig配置文件的方法。
用户解锁设备时,如果我们在解锁前已经发送过广播,则再次发送广播(额外的重新广播),避免尝试加载运营商配置、SIM卡仍在加载时,发生解锁事件。
当收到包发生改变的广播时,也就是系统第一次启动和配置文件的包发生改变,会触发EVENT_PACKAGE_CHANGED事件重新读取CarrierConfig配置文件。
2、加载CarrierConfig
UICC框初始化完成后,创建了个Subscription框架(在我梳理sim卡初始化的流程中有说到),其中的SubscriptionInfoUpdater类,在这个类中有一个updateCarrierServices方法。
热插拔sim卡都会调用这个方法,在这个方法中刷新配置。(所以重新拔插SIM卡会重新读配置文件)
虽然方法名称相同,但是这个方法调用的updateConfigForPhoneId()方法是CarrierConfigManagerd的。
不过,CarrierConfigManagerd在这个方法进行了AIDL通信,它使用的是ICarrierConfigLoader.aidl对外暴露的updateConfigForPhoneId方法,归根溯源还是调用的是CarrierConfigLoader.updateConfigForPhoneId();进行刷新配置。
我们看看updateConfigForPhoneId()方法它这个方法具体做了哪些事情
最终处理逻辑是在CarrierConfigLoader的内部类ConfigHandler处理器中执行的。先获取默认的配置,然后再通知Handler处理器获取运营商定制的配置。
1)获取默认配置
2)当默认配置未配置
在bindToConfigPackage方法中绑定默认服务,然后在EVENT_CONNECTED_TO_DEFAULT事件处理,连接默认配置的服务开始读取默认Config。
3)默认配置完毕,开始执行运营商配置的读取
4)获取运营商配置
5)当运营商配置未配置
同样的,在当在bindToConfigPackage方法中绑定运营商重写的服务。绑定成功后执行EVENT_CONNECTED_TO_CARRIER消息开始读取运营商Config。
6)读取carrier_config.xml文件
EVENT_CONNECTED_TO_CARRIER和EVENT_CONNECTED_TO_DEFAULT两个消息,都是通过ICarrierService的getCarrierConfig()方法来获取最新要加载的config。
与ICarrierConfigLoader.aidl相似地,我们调用AIDL的getCarrierConfig()方法,其具体实现是在CarrierService.java的内部类ICarrierServiceWrapper中。
我们在该方法中使用从事件处理传过来的carrierID(运营商id),使用onLoadConfig()方法来获取到最新要加载的config。
我看了一下,这个方法在CarrierService中是抽象方法,找了好久才发现了实现类在DefaultCarrierConfigService中(敲重点,这个类是实现读取配置文件的类,读取过程、读取参数都可以从这里得到。不同平台可能会对这个类进行重写,本文只针对展锐平台的项目)。
DefaultCarrierConfigService类路径:packages/apps/CarrierConfig/src/com/android/carrierconfig/DefaultCarrierConfigService.java
这个方法会读取assets文件夹和res文件夹中的配置文件,返回我们在配置文件定制的运营商配置。
读取assets(packages/apps/CarrierConfig/assets)文件夹中的配置文件时,我们需要使用运营商id或者mccmnc来索引。运营商id可以通过TelephonyManager.getSimCarrierId()方法获得。
需要注意的是以mccmnc命名的配置文件是针对那些没有匹配的运营商id的配置文件,当没有的id配置文件被添加到运营商id列表时,会被重新命名运营商id(如果运营商的id不是TelephonyManager.UNKNOWN_CARRIER_ID,为了提高可读性,不应该用来搜索assets文件)
也就是说,如果使用运营商id没有索引到配置文件,那么我们将使用MCC+MNC去索引配置文件名称。
xml不支持多个带过滤器的包,因为每个运营商(包括MVNOs)都有自己的配置文件,以其运营商id命名。vendor.xml和MCC+MNC.xml文件都可能包含多个带有过滤器的包。所有匹配的捆绑包被平铺,以返回一个运营商配置捆绑包。
从上面代码也可以看出,读取配置文件的优先级是:特殊运营商id.xml>运营商id.xml>mccmnc.xml。如果有运营商特殊id或运营商id的xml则选中它,没有将使用备份的mccmnc.xml结尾的配置文件。(特殊运营商id表示运营商id的pare_id,本文后面会说这两个id怎么查)
当读取完配置文件后,我们最后会读取res/xml/vendor.xml(packages/apps/CarrierConfig/res/xml/vendor.xml),将其添加到运营商config,可以覆盖掉之前的配置,这也是我们经常修改的地方。
根据项目的规划,运营商id.xml可以当作基线的默认配置,vendor.xml可以根据不同的项目做运营商需求定制。
大致流程就是:
在SIM卡加载完毕之后会开始读取运营商配置文件,当缓存中有配置文件就直接使用,如果没有将会读取assets文件夹以及res文件夹中的xml文件内容。
我们最终将读取到的config返回给ResultReceiver,在ResultReceiver中获取到config之后,会保存到用iccid作为文件名的xml中。
Xml文件保存在手机中的位置为:/data/user_de/0/com.android.phone/files/carrierconfig-com.android.carrierconfig-(iccId).xml
同时也会赋值给mConfigFromDefaultApp,在Hanlder处理器中处理EVENT_FETCH_CARRIER_DONE消息和EVENT_SUBSCRIPTION_INFO_UPDATED消息,将broadcastConfigChangedIntent广播通知监听carrier config变化的地方。
补充一点,当我们目前只知道mccmnc码时,可以通过packages/providers/TelephonyProvider/assets/sdk29_carrier_id/carrier_list.textpb这个文件来找到对应的运营商id
canonical_id就是运营商id,parent_canonical_id就是运营商的父id也就是我前面说的特殊运营商id。这样我们就能通过mccmnc找到运营商id了。
三、删除CarrierConfig(删)
1、清除config缓存
在上面我们理解了CarrierConfig在卡正常插入后的加载流程,其中CarrierConfigLoader的updateConfigForPhoneId()方法还会处理SIM卡拔出、IO异常、受限、未完成准备等状态。
如果SIM卡拔出、IO异常、受限、未完成准备时,是要清除CarrierConfig缓存的。清除缓存并不是删除手机中存储的xml文件,而是将mConfigFromDefaultApp和mConfigFromCarrierApp置为NULL。
所以,当我们将单编后的CarrierConfig.apk或修改系统内user_de保存的xml文件,刷进手机后,想让配置生效单纯热插拔SIM卡是不够的,需要在Settings中重置网络配置(settings->System->Advanced→Reset options),让xml文件重新读取创建mConfigFromDefaultApp和mConfigFromCarrierApp对象。
2、删除存储config的xml文件
config清除只是引用置空清除掉缓存,那保存的XML文件什么时候删除呢?这个是清除的方法:
1)系统更新
也就是CarrierConfigLoader重新被创建时
2)收到包改变的广播
当CarrierConfigLoader内部的ConfigLoaderBroadcastReceiver广播接收器收到包内容发生改变的广播时
所以,我们修改完配置文件直接push进去,也可以用am broadcast命令手动发送一个广播清除网络配置。
四、获取CarrierConfig的信息(查)
1、通过PhoneGlobal
因为CarrierConfigLoader就是在PhoneGlobal初始化的,所以我们可以通过PhoneGlobal对象的方法,调用CarrierConfigLoader对象直接获取CarrierConfig方法。
PhoneGlobal是单例模式,对外暴露了getInstance方法可以获取PhoneGlobal对象。
2、通过ServiceManager
前面我们说到CarrierConfigLoader在构造器将本身服务注册进了ServiceManager里面,所以对于系统应用可以通过getSystemService的方法获取到该服务的接口。
我就在SubscriptionInfoUpdater刷新CarrierConfig之后打印一下获取的信息。看看行不行
能拿到,看看打印的日志:
拿到的这个数据包是一个PersistableBundle对象,继承于BaseBundle,BaseBundle提供了getString、getByte、getChar、getBoolean等方法可以取出数据来使用。
五、修改CarrierConfig信息(改)
1、CarrierConfig修改运营商名称
在SIM卡流程中,UiccProfile如果收到运营商配置改变的广播,会处理运营商名称的覆盖,在这个方法中会读取CarrierConfig和Spn-config.xml文件来覆盖运营商名称(早些年的版本是在SimRecord中修改的)
覆盖运营商显示名称的读取顺序是,CarrierConfig>Spn-config.xml,更详细的业务可以去这个方法看,可以修改成自己的逻辑。
涉及到SPN的修改,spn的修改可以分为入数据库前修改、入数据库后修改、显示前修改。这个可以单独写一篇文章,这个carrierConfig的修改SPN比较少用,我们大多数时候是在Spn-config.xml中或SST中修改。
所以我们只要在Carrier Config配置文件中添加上修改运营商名称的相关参数就可以实现运营商名称的修改。
配置文件内容大致是这样:
配置文件中以键值对形式保存的Boolean参数,一般用作开关;以键值对形式保存的string文本可以被读取后使用。
从上图可以看出展锐平台默认是把VoLTE、和漫游关闭掉的(未配置的参数一般在CarrierConfigManger.java设置默认值的,一般是false)。
我们修改一下当前手机卡运营商id对应的CarrierConfig.xml,将我们自定义的运营商名称配置写上,并设置启用的开关参数。
编译CarrierConfigAPP,刷进手机,重置网络配置:
查看一下结果:
搞定。
最后再补充一个常见的问题,当我们需要实现某个需求但是不知道要配置什么参数时,例如我现在要将漫游默认开启,但是我不知道要配置什么参数,我们可以在CarrierConfigManger.java找到。
我在这个类翻了一番,找到了这个就是默认开启漫游的参数,只要将他复制一下添加到配置文件里面去,设置为true就好了,下面我就演示一下开启漫游。
2、CarrierConfig修改IMS配置
修改漫游和4g通话功能。
编译CarrierConfigAPP,刷进手机,删掉缓存中的配置文件重置网络配置:
最后有个vendor.xm修改没有讲,但是文件格式和CarrierID.xml是一样的,只不过他相当于是二次定制文件,无论前面的读取顺序如何,最后都会vendor.xml中的配置作为补充添加到config对象去。
六、总结
修改Carrier Config大致流程就是这样,修改配置很简单,根据需求文档配置参数就可以,但是读取流程是我们必须掌握的。因为不同平台可能对配置文件的读取顺序有一定的差异,学会了对深度定制有着很大的帮助,可以让你不只局限于配置的修改。对参数的熟悉需要经验的积累,改多了,需求来了就知道要增删查改什么参数了。