该文档主要是基于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();

    }

语音信箱流程分析 voice mail number


上面红色方框里面的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 里面得到对应的值.

 语音信箱流程分析 voice mail number

 

真正的发起加载和解析这个xml 文件的发起者还是SIMRecords.java.

语音信箱流程分析 voice mail number

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

语音信箱流程分析 voice mail number

r.registerForRecordsLoaded(this, EVENT_SIM_RECORDS_LOADED, null); 这行代码会监听到系统识别sim 卡相关的信息完成了.然后发送EVENT_SIM_RECORDS_LOADED 消息来更新sim 卡的语音信箱. 当相应卡槽更换SIM卡后,会更新之前SIM卡的VM number设置

 语音信箱流程分析 voice mail 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()的分析在虚拟运营商的语音信箱的分析 这一节

语音信箱流程分析 voice mail number


下面的函数是mtk 原始的设计

 语音信箱流程分析 voice mail number

 

语音信箱号码的修改和保存的流程

如何我们使用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

 语音信箱流程分析 voice mail number

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 方法来处理长按时间

 语音信箱流程分析 voice mail number

 

 语音信箱流程分析 voice mail number

语音信箱流程分析 voice mail number

语音信箱流程分析 voice mail number

上面的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

语音信箱流程分析 voice mail number

editor.putString(VM_NUMBER + mySimId, number); 改成

editor.putString(getMvnoVoiceMailNumberKey(), number);

 

frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/SIMRecords.java

语音信箱流程分析 voice mail number


setVoiceMailByCountry 的参数原本是operator也就是mcc+mnc,处于MVNO的考虑,我们这里把他改成getMvnoVoiceMailNumberKey() 方法的返回值.

 

getMvnoVoiceMailNumberKey() 方法具体的实现代码如下(SIMRecords GSMPhone 是一样的.

语音信箱流程分析 voice mail number

目前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

 语音信箱流程分析 voice mail number

 

增加isNetworkRoaming 方法

语音信箱流程分析 voice mail number

 

上面这种方法是将需要配置漫游状态下使用不同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

 

相关文章: