该文档主要是基于Android JB 版本,和可能和KK版本有一定的差异.如果在KK上面有不一样的地方,也可以参考JB上面的思路来分析相关的问题
以下分析是基于mtk的源码,不是Android 原始的code.我会把mtk 相关的code 提取出来,以便和Android 原生的code 对比.
如何配置语音信箱
具体可以参考: FAQ04505
有些SIM卡在出厂时并没有预置VoiceMail number,但运营商又要求能够根据PLMN去自适应的从手机中读取到预设的VM number。在此介绍以xml的方式预置VM number的方法,以及如何允许用户去修改并能够记住用户的选择。VM number使用的优先级为: SIM卡读取>用户设置>xml预置。在用户修改voice mail number时,优先存储到SIM卡。若SIM卡存储失败,则以IMSI为单位存储到手机中。
以XML的方式预置VM number,文件名为:voicemail-conf.xml
文件的内容格式为
<?xml version='1.0' encoding='utf-8'?>
<voicemail>
<voicemail numeric="46000" carrier="CMCC" vmnumber="10086" vmtag="CMCC voicemail" />
</voicemail>
如果遇到虚拟运营商,另外分析.
关于文件的位置
文件在手机中的位置:system/etc
文件在工程中的位置: mediatek\frameworks\base\telephony\etc\voicemail-conf.xml
还需要在build\target\product\common.mk中,添加语句PRODUCT_COPY_FILES += mediate/frameworks/base/telephony/etc/voicemail-conf.xml:system/etc/voicemail-conf.xml
使SIM卡中的VM number优先于预置号码的方法
将SIMRecords.java (
JB3之前版本:frameworks\base\telephony\java\com\android\internal\telephony\gsm
JB3,JB5:frameworks\opt\telephony\src\java\com\android\internal\telephony\gsm)
的函数private setVoiceMailByCountry(String spn)中的语句if (mVmConfig.containsCarrier(spn))修改为
if (TextUtils.isEmpty(voiceMailTag) && TextUtils.isEmpty(voiceMailNum) && mVmConfig.containsCarrier(spn))
在使用了voicemail-conf.xml来预置VM number后,使终端用户可以修改VM number的方法
1) 在SIMRecords.java中添加语句import android.text.TextUtils;
2) 在SIMRecords.java中添加一个成员变量boolean isSetByCountry = false;
3) 将SIMRecords.java中的函数private setVoiceMailByCountry(String spn)修改为
private void setVoiceMailByCountry(String spn) {
if (TextUtils.isEmpty(voiceMailTag) && TextUtils.isEmpty(voiceMailNum) && mVmConfig.containsCarrier(spn))
{
// isVoiceMailFixed = true; //注释掉此语句以让用户能够修改
isSetByCountry = true; //让GsmPhone知道这是从xml中读取的
voiceMailNum = mVmConfig.getVoiceMailNumber(spn);
voiceMailTag = mVmConfig.getVoiceMailTag(spn);
}
4) 在GSMPhone.java (
JB3之前版本:frameworks\base\telephony\java\com\android\internal\telephony\gsm
JB3,JB5:frameworks\opt\telephony\src\java\com\android\internal\telephony\gsm)中的函数handleMessage中的语句case EVENT_SIM_RECORDS_LOADED:中将语句 if (imsi != null && imsiFromSIM != null && !imsiFromSIM.equals(imsi))
{
…
}
修改为
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
// 当相应卡槽更换SIM卡后,是否清除用户对之前SIM卡的VM number设置
boolean clear_if_change_sim = sp.getBoolean(“clear_if_change”, false);
if (clear_if_change_sim && imsi != null && imsiFromSIM != null && !imsiFromSIM.equals(imsi)){
//storeVoiceMailNumber(null);
Log.d(LOG_TAT, “reset vm number because sim changed”);
SharedPreferences.Editor editor = sp.edit();
editor.remove(getVmSimImsi());
editor.apply();
setVmSimImsi(null);
}
5) 将GSMPhone.java中的函数private void storeVoiceMailNumber(String number)中的语句editor.putString(VM_NUMBER+mySimId, number);
修改为editor.putString(getSubscriberId(), number);
//不再使用卡槽作为保存VM number的单位,而使用IMSI
6) 将GSMPhone.java中的函数public String getVoiceMailNumber()中的语句
if (TextUtils.isEmpty(number))
修改为
if (TextUtils.isEmpty(number) || ((SIMRecords)mIccRecords.get())).isSetByCountry)
//如果SIM卡中无VM number或是通过voicemail-conf.xml来设置的,则应该读取一下Preference,看是否用户对此SIM卡设置过VM number。
并且将语句number = sp.getString(VM_NUMBER+mySimId, null);修改为
Log.d(LOG_TAG, vm num from simRecords, num= “+number+” is from factory= “+ ((SIMRecords)mIccRecords).isSetbyCountry);
String temp = sp.getString(getSubscriberId(), null);
if (temp != null)
{
Log.d(LOG_TAG, “replace vm num with user defined, num= “+temp);
number = temp;
}
流程分析
在看这一节的时候,请先看一下如何配置语音信箱 这一节,因为下面的分析是建立在上面这一节的基础之上的.
voicemail-conf.xml 加载和解析
KK:frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/VoiceMailConstants.java
JB: frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/VoiceMailConstants.java
配置在xml 文件里面的语音信箱的解析是VoiceMailConstants 来负责的.在创建VoiceMailConstants 这个类的时候,就直接调用了loadVoiceMail(); 来解析
VoiceMailConstants() {
CarrierVmMap = new HashMap<String, String[]>();
loadVoiceMail();
}
上面红色方框里面的data 变量对应的就是类似下面的配置.
<voicemail numeric="46000" carrier="" vmnumber="10086" vmtag="Voice Mail" />
在不考虑虚拟运营商的情况下:
numeric 就是mcc + mnc
vmnumber 就是要求预制的语音信箱号码
vmtag 就是语音信箱的名称
carrier 就是一个备注的名称,用于给开放人员方便区分不同sim卡的,可以随便写
所有的配置的信息都保存在CarrierVmMap 这个映射对应里面.
如果voicemail-conf.xml 里面配置的参数里面有numeric(mcc+mnc) 相同的属性值的话,后面的会覆盖前面的.原因很简单,这和java 里面的Map 对象有关系.
需要调用的时候根据获取到的sim 卡的spn 来在CarrierVmMap 这个HashMap 里面得到对应的值.
真正的发起加载和解析这个xml 文件的发起者还是SIMRecords.java.
SIMRecords.java路径:
frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/SIMRecords.java
语音信箱号码的读取
在sim 卡识别完成之后,系统会去根据mcc +mnc 来获取这张卡的spn,再根据spn 设置这张卡的语音信箱号码.
frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GSMPhone.java
r.registerForRecordsLoaded(this, EVENT_SIM_RECORDS_LOADED, null); 这行代码会监听到系统识别sim 卡相关的信息完成了.然后发送EVENT_SIM_RECORDS_LOADED 消息来更新sim 卡的语音信箱. 当相应卡槽更换SIM卡后,会更新之前SIM卡的VM number设置
一般发起读取sim 卡语音信箱的是phone 进程.
Phone 进程会调用framework 里面的PhoneProxy.java 的getVoiceMailNumber方法来调用GSMPhone.java 的getVoiceMailNumber 方法
frameworks/opt/telephony/src/java/com/android/internal/telephony/PhoneProxy.java
public String getVoiceMailNumber() { return mActivePhone.getVoiceMailNumber(); } |
在调用GSMPhone.java 的getVoiceMailNumber 方法的时候,会进行判断.
如果SIM卡中无VM number或是通过voicemail-conf.xml来设置的,则应该读取一下Preference,看是否用户对此SIM卡设置过VM number。
其中标记1的地方是获取运营商本身写到sim卡里面的语言信箱号码.
标记2 的地方是判断运营商是否在sim 卡设置VM mumber 和用户是否自己重新设置过VMnumber.
isSetByCountry 这个布尔值变量就是后来我们自己增加的,用来区分用户自己是否改过这个VM number
标记3中的是根据spn 来获取对应的VM number的,mtk 原始的设计不是这样的,而是使用卡槽和sim卡id 来区别的,我这里由于项目的需要,涉及到虚拟运营商,所以使用的spn来区别.而且我这边在设置的时候也把这个key 改成了使用spn.
关于getMvnoVoiceMailNumberKey()的分析在虚拟运营商的语音信箱的分析 这一节
下面的函数是mtk 原始的设计
语音信箱号码的修改和保存的流程
如何我们使用voicemail-conf.xml 来配置了VM number 之后,是否是否可以手动修改呢,结果是肯定可以的,但是要注意手动修改的VM numbe和xml 预制的VM number的优先级情况.
前面已经提到几种VM number 的优先级情况:
VM number使用的优先级为: SIM卡读取>用户设置>xml预置。在用户修改voice mail number时,优先存储到SIM卡。若SIM卡存储失败,则以IMSI为单位存储到手机中
frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GSMPhone.java
Mtk 原始的设计在保存VM number 的时候是使用的卡槽来区分.我这边这里修改之后是使用的
关于getMvnoVoiceMailNumberKey()的分析在虚拟运营商的语音信箱的分析 这一节
App层和framework 层是怎么交互的?
相关的文件
JB:
packages/apps/Phone/src/com/mediatek/settings/VoiceMailSetting.java
KK:
packages/services/Telephony/src/com/mediatek/settings/VoiceMailSetting.java
packages/apps/Dialer/src/com/android/dialer/dialpad/DialpadFragment.java
从拨号盘按1到拨出电话:
我们所看到拨号盘页面也就是DialpadFragment.java 文件,该文件实现了View.OnLongClickListener 接口,所以它本身就可以实现onLongClick 方法来处理长按时间
上面的getVoicemailIntent() 方法返回的intent 里面并没有具体的voicemail number ,那么应用到底是在那里处理才会拿到这个号码呢?
虚拟运营商的语音信箱的分析(配置,读取)
这里主要是介绍和MNO 不一样的地方,因为MVNO 和MNO 在这一块大部分是一样的.
什么是mvno?
MVNO(Mobile Virtaul Network Operator)虚拟网络运营商,没有自己的实体网络,通过租用MNO(Mobile Network Operator)的网络来提供网络服务。
MVNO 的区分方式
目前MTK支持区分MVNO的方式有四种(KK以前没有EF_GID1方式),每种区分方式对应一个xml的配置表:
1. EF_SPN方式,对应MVNO配置到Virtual-spn-conf-by-efspn.xml中
2. EF_IMSI方式,对应MVNO配置到Virtual-spn-conf-by-imsi.xml中
3. EF_PNN方式,对应MVNO配置到Virtual-spn-conf-by-efpnn.xml中
4. EF_GID1方式,对应MVNO配置到Virtual-spn-conf-by-efgid1.xml中
MVNO VM number 如何配置
Mvno 和 MNO 的mcc mnc 都是一样的.我们前面在voicemail-conf.xml 加载和解析这一节中提到如果sim 卡的mcc+mnc 是一样的,那么他们的VM number 就是一样的.但是MVNO 和他所租用的运营商不是同一个运营商,所以MVNO的语音信箱和spn 经常会不一样.所以如果涉及到MVNO的时候,需要在 如何配置语音信箱这一节的基础上面再做如下修改.
frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GSMPhone.java
将editor.putString(VM_NUMBER + mySimId, number); 改成
editor.putString(getMvnoVoiceMailNumberKey(), number);
frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/SIMRecords.java
setVoiceMailByCountry 的参数原本是operator也就是mcc+mnc,处于MVNO的考虑,我们这里把他改成getMvnoVoiceMailNumberKey() 方法的返回值.
getMvnoVoiceMailNumberKey() 方法具体的实现代码如下(SIMRecords 和GSMPhone 是一样的.
目前Android JB 版本支持的MVNO 就是通过上面涉及到的3个方式来分区的,也就是spn,pnn,imsi.具体可以参考MVNO 的区别方式 这一节.
漫游状态下和非漫游状态的下配置不同的语音信箱号码
同一个mcc+mnc 可以对应有多个运营商,在这个时候,他们的可以都有自己VM number.
那么如果同一张sim 卡在使用不同的网络的时候,如果要求它的VM number 跟着所使用的网络的运营商来变也是可以的.其中一种就是漫游和非漫游情况下的配置不同语音信箱的配置.
原理分析:在用户需要获取VM number 的时候,我们去判断当前sim 卡是否处于漫游状态,如果,如果处于漫游状态,我们就去改变的.具体修改方法如下:
方法一(少部分sim 有要求)
frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GSMPhone.java
增加isNetworkRoaming 方法
上面这种方法是将需要配置漫游状态下使用不同VM number 的sim 卡的mcc + mnc 和对应的漫游状态下的VM number 配置2个数组里面,在有这种要求的sim 的sim 卡数量少的时候,比较适合.如果遇到数量很多的情况下,可以按照方法2的思路来改.
方法2
在voicemail-conf.xml 中的配置可以按照下面的格式添加roamingvm 这一属性, roamingvm 的属性值就是对应的sim 卡(23433 ) 在漫游状态下的VM number , vmnumber 就是非漫游状态下的VM number .
<voicemail numeric="23433" carrier="Orange" vmnumber="+447973100123" vmtag="Voicemail" roamingvm="+447973100123"/>
同时参考voicemail-conf.xml 这一节来处理roamingvm 这一项.然后在GSMPhone.java 的getVoiceMailNumber 方法按照下面的思路来修改:如果当前sim 是漫游状态,就使用roamingvm 这一属性值来作为其VM number ,这个时候就需要在VoiceMailConstants.java
中增加一个方法来专门获取roamingvm 这一属性值.
KK:frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/VoiceMailConstants.java
JB: frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/VoiceMailConstants.java
增加方法和变量
static final int NUMBER_Roaming = 5; String getVoiceMailNumberRoaming(String carrier) { String[] data = CarrierVmMap.get(carrier); return data[NUMBER]; } |
同时在private void loadVoiceMail() 做如下红色部分修改
转载自原文链接, 如需删除请联系管理员。
原文链接:语音信箱流程分析 voice mail number,转载请注明来源!