安卓本身并不支持双卡,在定制过之后,默认情况下两张卡的来电铃声是一样的,不能分别进行设置,这就需要添加一些相关代码。

基本思路是,找到来电时播放铃声的地方,修改为根据卡的ID播放相应的声音。(而整个来电流程比较复杂,可以参考其他一些分析来电流程的文章来进行了解,这里就不赘述)

以Android6.0为例,控制通话的上层代码在android\packages\services下,而控制来电铃声的在Telecomm里面。

有两个相关类:

Telecomm\src\com\android\server\telecom\Ringer.java

Telecomm\src\com\android\server\telecom\AsyncRingtonePlayer.java

AsyncRingtonePlayer封装了一个用来播放铃声的类,Ringer是用来控制播放铃声的。

在来电时会调用Ringer的startRingingOrCallWaiting方法播放铃声。

private void startRingingOrCallWaiting(Call call) {

Call foregroundCall = mCallsManager.getForegroundCall();

Log.v(this, "startRingingOrCallWaiting, foregroundCall: %s.", foregroundCall);

if (mRingingCalls.contains(foregroundCall) && (!mCallsManager.hasActiveOrHoldingCall())) {

// The foreground call is one of incoming calls so play the ringer out loud.

stopCallWaiting(call);

if (!shouldRingForContact(foregroundCall.getContactUri())) {

return;

}

AudioManager audioManager =

(AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

if (audioManager.getStreamVolume(AudioManager.STREAM_RING) >= 0) {

if (mState != STATE_RINGING) {

Log.event(call, Log.Events.START_RINGER);

mState = STATE_RINGING;

}

mCallAudioManager.setIsRinging(call, true);

// Because we wait until a contact info query to complete before processing a

// call (for the purposes of direct-to-voicemail), the information about custom

// ringtones should be available by the time this code executes. We can safely

// request the custom ringtone from the call and expect it to be current.

mRingtonePlayer.play(foregroundCall.getRingtone());

} else {

Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");

}

if (shouldVibrate(mContext) && !mIsVibrating) {

mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,

VIBRATION_ATTRIBUTES);

mIsVibrating = true;

}

} else if (foregroundCall != null) {

...

}

}

源码中的注释还是比较清楚地,在正常状态下来电时,调用mRingtonePlayer.play(foregroundCall.getRingtone());播放铃声。所以我们要做的就是在调用这个方法前进行双卡的区分。

在AsyncRingtonePlayer中添加一个方法,来标识当前来电的卡ID:

private int id;

public void setID(int id){

this.cid = id;

}

之后在播放前调用这个方法:

try{

int id = SubscriptionManager.getPhoneId(Integer.valueOf(

foregroundCall.getTargetPhoneAccount().getId()));

mRingtonePlayer.setcCID(id);

}catch(Exception e){

mRingtonePlayer.setcCID(0);

}

mRingtonePlayer.play(foregroundCall.getRingtone());

不同平台的获取ID的方法可能有所不同,移植时注意。

然后主要看AsyncRingtonePlayer中的播放逻辑:

void play(Uri ringtone) {

Log.d(this, "Posting play.");

//调用postMessage进行通知

postMessage(EVENT_PLAY, true /* shouldCreateHandler */, ringtone);

}

private void postMessage(int messageCode, boolean shouldCreateHandler, Uri ringtone) {

synchronized(this) {

if (mHandler == null && shouldCreateHandler) {

mHandler = getNewHandler();

}

if (mHandler == null) {

Log.d(this, "Message %d skipped because there is no handler.", messageCode);

} else {

//发送消息

mHandler.obtainMessage(messageCode, ringtone).sendToTarget();

}

}

}

private Handler getNewHandler() {

Preconditions.checkState(mHandler == null);

HandlerThread thread = new HandlerThread("ringtone-player");

thread.start();

return new Handler(thread.getLooper()) {

@Override

public void handleMessage(Message msg) {

switch(msg.what) {

case EVENT_PLAY:

//调用handlePlay播放

handlePlay((Uri) msg.obj);

break;

case EVENT_REPEAT:

handleRepeat();

break;

case EVENT_STOP:

handleStop();

break;

}

}

};

}

private void handlePlay(Uri ringtoneUri) {

// don't bother with any of this if there is an EVENT_STOP waiting.

if (mHandler.hasMessages(EVENT_STOP)) {

return;

}

ThreadUtil.checkNotOnMainThread();

Log.i(this, "Play ringtone.");

if (mRingtone == null) {

// 获取一个Ringtone对象

mRingtone = getRingtone(ringtoneUri);

// Cancel everything if there is no ringtone.

if (mRingtone == null) {

handleStop();

return;

}

}

handleRepeat();

}

private void handleRepeat() {

if (mRingtone == null) {

return;

}

if (mRingtone.isPlaying()) {

Log.d(this, "Ringtone already playing.");

} else {

//开始播放

mRingtone.play();

Log.i(this, "Repeat ringtone.");

}

// Repost event to restart ringer in {@link RESTART_RINGER_MILLIS}.

synchronized(this) {

if (!mHandler.hasMessages(EVENT_REPEAT)) {

mHandler.sendEmptyMessageDelayed(EVENT_REPEAT, RESTART_RINGER_MILLIS);

}

}

}

我们需要干预的就是获取Ringtone这一过程,以下时getRingtone:

private Ringtone getRingtone(Uri ringtoneUri) {

if (ringtoneUri == null) {

ringtoneUri = Settings.System.DEFAULT_RINGTONE_URI;

}

Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri);

if (ringtone != null) {

ringtone.setStreamType(AudioManager.STREAM_RING);

}

return ringtone;

}

可以看出在没有传递ringtoneUri(这个主要是在对一些号码单独设置铃声时才会有值,一般都为null)时,ringtoneUri取得是Settings.System.DEFAULT_RINGTONE_URI;而这个值:

public static final Uri DEFAULT_RINGTONE_URI = getUriFor(RINGTONE);

和Settings模块中设置的来电铃声是一个值。

所以,关键点就是根据之前设置的那个卡id,来动态的改变这里的ringtoneUri 。系统默认使用RINGTONE这个字段来保存来电铃声,添加双卡区分后,我们可以重新定义一个系统属性来保存卡二的铃声URI。不过Android已经为我们预置了额外的两个字段,可以直接拿来使用:

public static final String RINGTONE_2 = "ringtone_2";

public static final String RINGTONE_3 = "ringtone_3";

剩下的内容就很简单了,写一个方法getUriBySubID(通过id取uri)来替换原来方法中当参数为null时的逻辑即可。

最后不要忘了在Settings模块中添加设置卡二铃声的入口,可以参考原来默认的入口,只需改改名字,改改保存的字段即可。

关于未设置时的默认铃声问题,在我的平台上RINGTONE_2 和RINGTONE_3 的默认值是和RINGTONE一样的,当然也可以指定,和设置普通的系统属性默认值一样。如果是自己新定义的系统属性,一定要注意默认值的问题。

Android代码双卡切换,Android添加双卡铃声设置的方法相关推荐

  1. android 图片绑定按钮,Android编程实现给Button添加图片和文字的方法

    本文实例讲述了Android编程实现给Button添加图片和文字的方法.分享给大家供大家参考,具体如下: //为按钮添加图片和文字的方法 public Spanned getSpan(int id, ...

  2. android代码里切换横竖屏,Android横竖屏切换

    Android 2.3以前的横竖屏切换: 在Android 2.3平台上,我们可以需要设置界面的横竖屏显示时,可以在AndroidManifest.xml中,对Activity的属性添加以下代码: A ...

  3. android 代码加view,Android中将View添加至窗口的源码分析

    本文主要内容是讲解一个视图View或者一个ViewGroup对象是如何添加至应用程序窗口中的.下文中提到的窗口可泛指我们能看到的界面,包括一个Activity呈现的界面(我们可以将之理解为应用程序窗口 ...

  4. android+tv+自动切换,Android TV 重写GridView,实现焦点放大效果

    关于缩放,使用了view.setScaleX/Y 方法,api11以上即可. 重写dispatchDraw(),绘制选中项的焦点效果.(注意带阴影的焦点图需要微调偏移量) 要将选中项绘制显示在顶层,所 ...

  5. android 表情键盘切换,Android仿微信键盘切换效果

    Android 仿微信的键盘切换(录音,表情,文字,其他),IM通讯,类似朋友圈只要涉及到文字等相关的app都会要涉及到键盘的处理,今天就给大家分享一下Android 仿微信的键盘切换. 效果图如下: ...

  6. android代码说明文档,android:label说明

    文章目录 1. 说明 android:label 用于app 在切换activity的时候,自动更换左上角的显示 2. 代码架构 3. String.xml 字符串资源文件My Application ...

  7. android代码关闭数据库,android – 我应该如何正确打开和关闭我的数据库

    我有一个应用程序,它将一些数据存储在SQLite数据库中.此外,我在我的应用程序中进行了大量查询和重新查询.我在其中有大约15个活动.并且所有人都使用数据库来查询数据. 但我正在做的是在每个活动中打开 ...

  8. android 导航自动切换,Android导航抽屉切换图标向右

    吃鸡游戏 我为EndDrawerToggle该类编写了一个与您的设置非常相似的设置- DrawerLayout带末端对齐的抽屉View,AppCompatActivity带有自定义Toolbar的支持 ...

  9. android 代码打开权限,android开发权限询问的示例代码

    现在基于信息安全问题,特别是版本是23以上权限越严格. 特别是拍照,读,写权限 一般权限允许过,下次就不用询问了的,所以很多应用都喜欢在首页或者启动页直接询问,不允许的就用不了1.下面给出封装好的类, ...

最新文章

  1. mysql表和表的关系_mysql表与表之间建关系
  2. python数字类型-Python数字类型及其操作
  3. 2!=5 or 0在python中是否正确-python 中 and or
  4. 半阈值化的应用说明及利用函数threshold实现半阈值化的方法
  5. 大型网站技术架构:核心原理与案例分析 mobi_阿里面试官:你会高并发技术吗?...
  6. liteIDE配置环境变量
  7. c语言有分数的怎么编,用C语言编程平均分数
  8. Study 1 —— HTML5概述
  9. P7295-[USACO21JAN]Paint by Letters P【平面图欧拉公式】
  10. 实用素材|UI设计师需要的输入框和表单
  11. thinkphp源码分析(三)—自动加载篇(Loader的分析)
  12. map迭代器遍历_一口气写了 HashMap 的 7种遍历方式,被同事夸了
  13. 空间复杂度怎么算_西餐厅主题餐饮空间设计装修预算怎么算?-雨川
  14. 大数据hadoop常见端口
  15. 天啊~ 少些一个等号的后果
  16. Java生成word 并导出简历
  17. cp 命令覆盖文件夹和文件
  18. FIX - 克隆虚拟机NAT模式网络不通、不稳定、vMnet8网络故障、网卡冲突、ssh连接慢
  19. vmware磁盘已成功扩展,从操作系统内部对磁盘进行重新分区
  20. 操作系统学习——分时操作系统

热门文章

  1. 论文梳理:3D ultrasound computer tomography: Hardware setup, reconstruction
  2. iCloud上传备份和下载的Demo
  3. 2021云栖大会丨大咖都讲了啥?
  4. 个人贷款违约预测模型
  5. 6.29逆水寒服务器维护,逆水寒6.29开服攻略及常用技巧大全
  6. 使用 goodsync 软件将指定目录的文档单向同步到 hexo 博客
  7. 用计算机计数 常常出错怎么办,“ULtra DMA CRC 错误计数”解决办法。
  8. 给新手准备的一些常用冷门知识点
  9. 贵州关于职称计算机考试试题,贵州2017年第一次职称计算机考试时间
  10. STM32 USB SD卡读卡器和NAND FLASH模拟U盘