Android MTK N 平台上如何添加双卡铃声功能
2017-10-30 11:32
567 查看
在 MTK M 平台上,是自带双卡铃声功能,因此部分客户升级到 N 平台之后,也会有这样的要求。那么我们就来看看如何实现这一功能。
实现这功能主要从 3 个模块入手,分别是 settings 上的设置显示界面、framework 上的 RingtoneManager 和 package/services/Telecomn 上的播放铃声的对应文件。
实现思路如下:首先我们需要在 settings 上,模仿设置卡一铃声的流程,添加一个设置卡二铃声的入口,同时在 RingtoneManager 上添加设置和获取卡二铃声的接口并保存起来,最后,在 Telecomn 模块上判断是哪张卡,并响起对应的铃声。
在 settings 中设置铃声的界面主要是由 SoundSettings.java 构成的,其对应的 xml 布局为 sound_settings,其中有以下代码:
上面 3 个 DefaultRingtonePreference 分别作为手机铃声,通知铃声和闹钟铃声的设置入口,目前我们仅修改手机铃声,主要关注 ringtoneType=”ringtone” 的 DefaultRingtonePreference。
在 SoundSettings.java 中,我们可以看到以下代码:
当我们点击 SoundSettings.java 中的 设置手机铃声的 DefaultRingtonePreference时,将会执行 if 语句中的代码,通过 onPrepareRingtonePickerIntent() 方法赋予 Intent 启动铃声选择界面的属性,并将其启动。
当我们选择完铃声后,会将结果返回来,在 onActivityResult() 中将事件交由对应的DefaultRingtonePreference执行,代码如下:
这里的 mRequestPreference 就是 ringtoneType=”ringtone” 的 DefaultRingtonePreference。然而在 DefaultRingtonePreference 中是没有实现 onActivityResult 的,因此我们需要分析它的父类 RingtonePreference。
需要注意的一点是,父类 RingtonePreference 与 M 版本不同,并不是我们所熟知的 由官方 SDK 提供的 android.preference.RingtonePreference,而是 settings 中继承自 android.support.v7.preference.Preference 的一个类。
在 RingtonePreference 的 onActivityResult 中的操作非常简单,就是从反回的 Intent 中获取了用户选择的铃声的 uri,并将其传入 onSaveRingtone() 方法中。
在 DefaultRingtonePreference 的 onSaveRingtone() 方法中只有下面一行代码:
DefaultRingtonePreference 就是通过 RingtoneManager 的 setActualDefaultRingtoneUri 方法 通知 framework 层的 RingtoneManager 铃声改变。
以上就是 settings 中铃声选择的相关功能的代码逻辑了,接下来我们在这基础上添加双卡铃声选择功能。
从上面的分析中我们可以知道,在点击选择手机铃声的 DefaultRingtonePreference 时,会走到该 DefaultRingtonePreference 的 onPrepareRingtonePickerIntent() 方法中,那么我们就从这里判断。当用户点击 DefaultRingtonePreference 时,我们判断此时是否有两张卡,如果判断是有的,则赋予 Intent 启动选择 SIM CARD 的功能,在选择完毕之后将 SIM CARD 的信息以 Intent 的形式返回,然后设置被选择的 SIM CARD 的铃声。如果没有两张卡,则直接判断当前的 SIM 卡,并设置它的铃声。
首先修改 DefaultRingtonePreference.java 如下:
在 onPrepareRingtonePickerIntent 方法中通过 getRingtoneType() 方法判断的类型,如果是TYPE_RINGTONE,则执行方法 initSIMSelectorIntent(),并根据该方法的返回值决定是否 retrun。 而 initSIMSelectorIntent 方法的主要目的是判断是否手机有两张卡,如果是,则将传入的 intent 的 action 设置为 ACTION_SIM_SETTINGS 用于启动 SIM 卡选择器,并返回 true,从而执行 if 语句中的 return 跳出 onPrepareRingtonePickerIntent() 方法。SIM选择器是沿用 Android M 版本上的SubSelectSettings.java,这里就不贴出来了,需要提醒一下的是要在Settings.java、SettingsActivity.java 和 AndroidManifest.xml 上为 SubSelectSettings 添加对应的代码,详情可查阅 Android M 版本。然后如果判断没有两张卡,则执行原本的逻辑:
语句,启动铃声选择器。
然后来看 onActivityResult 方法,当requestCode 为 REQUEST_CODE 的时候,代表我们接收的数据是从 SIM 卡选择器中返回的。从数据中获知用户选择的是哪张 SIM 卡之后,程序会调用 startPicker() 方法,该方法的目的在于启动一个 action 值为 RingtoneManager.ACTION_RINGTONE_PICKER 的 Intent 来启动铃声选择器。其中 getRingtonesPickListener().pick(ringtonePickerIntent, this) 的 pick() 方法定义在 SoundSettings.java 中,通过回调进行调用。
然后我们来看 SoundSettings.java。
SoundSettings.java 在初始化 Preference 的时候,通过方法 setRingtonesPickListener 给 DefaultRingtonePreference.java 设置并实现了一个 RingtonePickListener,实现的 pick() 非常简单,就是调用了自身的 startActivityForResult() 并传入 REQUEST_RINGTONE_PICK 作为选择铃声的 REQUECT_CODE.
调用 startActivityForResult() 之后,逻辑会执行到 SoundSettings 的 onActivityResult(),最终走到 DefaultRingtonePreference 的 onActivityResult(),执行以下语句:
最终执行到 DefaultRingtonePreference 的 onSaveRingtone() 方法,该方法通过 SubSelectSettings.java 返回来的数据判断应该给哪张 SIM 卡设置铃声,然后将其保存了起来。在 onSaveRingtone() 方法中,我们用到了 RingtoneManager.setActualDefaultRingtoneUriSim2() 方法,该方法是模仿 RingtoneManager.setActualDefaultRingtoneUri() 设计的,目的是给 SIM 卡二保存铃声。对应的还添加了 RingtoneManager.getActualDefaultRingtoneUriSim2() 方法。
可以看到,我们是通过 getSettingForTypeSim2 方法来判断铃声的类型的,在 getSettingForTypeSim2 方法中,我们模仿 getSettingForType 方法新加了 3 个常量用作存储 卡 2 铃声,这个比较简单,就不贴代码了,需要注意的是,public 权限的常量和方法需要加上 @hide 注解,否则就需要 update api 了。
packages\services\Telecomm\src\com\android\server\telecom\Ringer.java 的 startRinging() 方法中。
上面的代码有点长,但是思路很简单,就是通过 TelephonyManager 实例的 getSubIdForPhoneAccount() 方法获取到 subId,然后根据这个 ID 来获取到 slotId,判断响铃的是卡一还是卡二,并据此调用 RingtoneManager 的对应的方法获取铃声 Uri,并通过方法 foregroundCall.setRingtone(defaultUri) 进行设置。setRingtone 方法是一个新加的方法,目的就是动态设置播放的铃声。需要修改
packages\services\Telecomm\src\com\android\server\telecom\Ringer.java,如下:
这样一来,整个功能就完成了,如有疑问,欢迎留言,谢谢。
实现这功能主要从 3 个模块入手,分别是 settings 上的设置显示界面、framework 上的 RingtoneManager 和 package/services/Telecomn 上的播放铃声的对应文件。
实现思路如下:首先我们需要在 settings 上,模仿设置卡一铃声的流程,添加一个设置卡二铃声的入口,同时在 RingtoneManager 上添加设置和获取卡二铃声的接口并保存起来,最后,在 Telecomn 模块上判断是哪张卡,并响起对应的铃声。
Settings
在开始动手之前,我们先简单梳理下 settings 中相关功能的代码。在 settings 中设置铃声的界面主要是由 SoundSettings.java 构成的,其对应的 xml 布局为 sound_settings,其中有以下代码:
<!-- Phone ringtone --> <com.android.settings.DefaultRingtonePreference android:key="ringtone" android:title="@string/ringtone_title" android:dialogTitle="@string/ringtone_title" android:ringtoneType="ringtone" /> <!-- Default notification ringtone --> <com.android.settings.DefaultRingtonePreference android:key="notification_ringtone" android:title="@string/notification_ringtone_title" android:dialogTitle="@string/notification_ringtone_title" android:ringtoneType="notification" /> <!-- Default alarm ringtone --> <com.android.settings.DefaultRingtonePreference android:key="alarm_ringtone" android:title="@string/alarm_ringtone_title" android:dialogTitle="@string/alarm_ringtone_title" android:persistent="false" android:ringtoneType="alarm" />
上面 3 个 DefaultRingtonePreference 分别作为手机铃声,通知铃声和闹钟铃声的设置入口,目前我们仅修改手机铃声,主要关注 ringtoneType=”ringtone” 的 DefaultRingtonePreference。
在 SoundSettings.java 中,我们可以看到以下代码:
@Override public boolean onPreferenceTreeClick(Preference preference) { if (preference instanceof RingtonePreference) { mRequestPreference = (RingtonePreference) preference; mRequestPreference.onPrepareRingtonePickerIntent(mRequestPreference.getIntent()); startActivityForResult(preference.getIntent(), REQUEST_CODE); return true; } if (mExt.onPreferenceTreeClick(preference)) { return true; } return super.onPreferenceTreeClick(preference); }
当我们点击 SoundSettings.java 中的 设置手机铃声的 DefaultRingtonePreference时,将会执行 if 语句中的代码,通过 onPrepareRingtonePickerIntent() 方法赋予 Intent 启动铃声选择界面的属性,并将其启动。
当我们选择完铃声后,会将结果返回来,在 onActivityResult() 中将事件交由对应的DefaultRingtonePreference执行,代码如下:
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mRequestPreference != null) { mRequestPreference.onActivityResult(requestCode, resultCode, data); mRequestPreference = null; } }
这里的 mRequestPreference 就是 ringtoneType=”ringtone” 的 DefaultRingtonePreference。然而在 DefaultRingtonePreference 中是没有实现 onActivityResult 的,因此我们需要分析它的父类 RingtonePreference。
需要注意的一点是,父类 RingtonePreference 与 M 版本不同,并不是我们所熟知的 由官方 SDK 提供的 android.preference.RingtonePreference,而是 settings 中继承自 android.support.v7.preference.Preference 的一个类。
在 RingtonePreference 的 onActivityResult 中的操作非常简单,就是从反回的 Intent 中获取了用户选择的铃声的 uri,并将其传入 onSaveRingtone() 方法中。
在 DefaultRingtonePreference 的 onSaveRingtone() 方法中只有下面一行代码:
RingtoneManager.setActualDefaultRingtoneUri(getContext(), getRingtoneType(), ringtoneUri);
DefaultRingtonePreference 就是通过 RingtoneManager 的 setActualDefaultRingtoneUri 方法 通知 framework 层的 RingtoneManager 铃声改变。
以上就是 settings 中铃声选择的相关功能的代码逻辑了,接下来我们在这基础上添加双卡铃声选择功能。
从上面的分析中我们可以知道,在点击选择手机铃声的 DefaultRingtonePreference 时,会走到该 DefaultRingtonePreference 的 onPrepareRingtonePickerIntent() 方法中,那么我们就从这里判断。当用户点击 DefaultRingtonePreference 时,我们判断此时是否有两张卡,如果判断是有的,则赋予 Intent 启动选择 SIM CARD 的功能,在选择完毕之后将 SIM CARD 的信息以 Intent 的形式返回,然后设置被选择的 SIM CARD 的铃声。如果没有两张卡,则直接判断当前的 SIM 卡,并设置它的铃声。
首先修改 DefaultRingtonePreference.java 如下:
public class DefaultRingtonePreference extends RingtonePreference { ...... //add by chenmj private RingtonePickListener mRingtonePickListener; private int mSlotId = -1; private static final int SINGLE_SIMCARD = 1; private static final int REQUEST_RINGTONE_PICK = 20; private static final int REQUEST_CODE = 200; private static final String PREF_SLOT_ID_PREFERENCE = "default_slot_id"; private static final String ACTION_SIM_SETTINGS = "com.android.settings.sim.SELECT_SUB"; //end @Override public void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) { f08a //add by chenmj if(RingtoneManager.TYPE_RINGTONE == getRingtoneType() && initSIMSelectorIntent(ringtonePickerIntent)) { return; } ringtonePickerIntent.setAction(RingtoneManager.ACTION_RINGTONE_PICKER); //end super.onPrepareRingtonePickerIntent(ringtonePickerIntent); /* * Since this preference is for choosing the default ringtone, it * doesn't make sense to show a 'Default' item. */ ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false); } @Override protected void onSaveRingtone(Uri ringtoneUri) { //modify by chenmj List<SubscriptionInfo> subInfoList = SubscriptionManager .from(getContext()).getActiveSubscriptionInfoList(); if(subInfoList == null) { mSlotId = 0; } else if(subInfoList.size() == 1) { mSlotId = subInfoList.get(0).getSimSlotIndex(); } if(mSlotId == 1) { RingtoneManager.setActualDefaultRingtoneUriSim2(getContext(), getRingtoneType(), ringtoneUri); }else { RingtoneManager.setActualDefaultRingtoneUri(getContext(), getRingtoneType(), ringtoneUri); } }else { RingtoneManager.setActualDefaultRingtoneUri(getContext(), getRingtoneType(), ringtoneUri); //end } //add by chenmj private boolean initSIMSelectorIntent(Intent intent) { //final int numSlots = mTeleManager.getSimCount(); final int numSlots = SubscriptionManager.from(getContext()) .getActiveSubscriptionInfoCount(); if (numSlots > SINGLE_SIMCARD) { intent.setAction(ACTION_SIM_SETTINGS); return true; } return false; } @Override public boolean onActivityResult(int requestCode, int resultCode, Intent data) { if (data != null) { switch (requestCode) { case REQUEST_CODE: if(data.getAction() != null && data.getAction().equals(ACTION_SIM_SETTINGS)) { if (resultCode == Activity.RESULT_OK) { mSlotId = (int)data.getIntExtra(PhoneConstants.SLOT_KEY, -1); startPicker(); return true; } } break; case REQUEST_RINGTONE_PICK: return super.onActivityResult(requestCode, resultCode, data); default: break; } } return super.onActivityResult(requestCode, resultCode, data); } private void startPicker() { Intent ringtonePickerIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); super.onPrepareRingtonePickerIntent(ringtonePickerIntent); ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false); setIntent(ringtonePickerIntent); getRingtonesPickListener().pick(ringtonePickerIntent, this); } public RingtonePickListener getRingtonesPickListener() { return mRingtonePickListener; } public void setRingtonesPickListener(RingtonePickListener listener) { mRingtonePickListener = listener; } public interface RingtonePickListener { void pick(Intent intent, DefaultRingtonePreference preference); } //end ...... }
在 onPrepareRingtonePickerIntent 方法中通过 getRingtoneType() 方法判断的类型,如果是TYPE_RINGTONE,则执行方法 initSIMSelectorIntent(),并根据该方法的返回值决定是否 retrun。 而 initSIMSelectorIntent 方法的主要目的是判断是否手机有两张卡,如果是,则将传入的 intent 的 action 设置为 ACTION_SIM_SETTINGS 用于启动 SIM 卡选择器,并返回 true,从而执行 if 语句中的 return 跳出 onPrepareRingtonePickerIntent() 方法。SIM选择器是沿用 Android M 版本上的SubSelectSettings.java,这里就不贴出来了,需要提醒一下的是要在Settings.java、SettingsActivity.java 和 AndroidManifest.xml 上为 SubSelectSettings 添加对应的代码,详情可查阅 Android M 版本。然后如果判断没有两张卡,则执行原本的逻辑:
super.onPrepareRingtonePickerIntent(ringtonePickerIntent)
语句,启动铃声选择器。
然后来看 onActivityResult 方法,当requestCode 为 REQUEST_CODE 的时候,代表我们接收的数据是从 SIM 卡选择器中返回的。从数据中获知用户选择的是哪张 SIM 卡之后,程序会调用 startPicker() 方法,该方法的目的在于启动一个 action 值为 RingtoneManager.ACTION_RINGTONE_PICKER 的 Intent 来启动铃声选择器。其中 getRingtonesPickListener().pick(ringtonePickerIntent, this) 的 pick() 方法定义在 SoundSettings.java 中,通过回调进行调用。
然后我们来看 SoundSettings.java。
public class SoundSettings extends SettingsPreferenceFragment implements Indexable { ...... //add by chenmj private static final int REQUEST_RINGTONE_PICK = 20; private RingtonePreference mRequestPickPreference; //end ...... @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { //add by chenmj if(requestCode == REQUEST_RINGTONE_PICK && mRequestPickPreference != null) { mRequestPickPreference.onActivityResult(requestCode, resultCode, data); return; } //end if (mRequestPreference != null) { mRequestPreference.onActivityResult(requestCode, resultCode, data); mRequestPreference = null; } } ...... private void initRingtones() { ...... //add by chenmj setRingtonesPickListener(); //end } ...... //add by chenmj private void setRingtonesPickListener() { RingtonePickListener ringtonePickListener = new RingtonePickListener() { @Override public void pick(Intent intent,DefaultRingtonePreference preference) { mRequestPickPreference = preference; SoundSettings.this.startActivityForResult(intent, REQUEST_RINGTONE_PICK); } }; ((DefaultRingtonePreference) getPreferenceScreen() .findPreference(KEY_PHONE_RINGTONE)).setRingtonesPickListener(ringtonePickListener); ((DefaultRingtonePreference) getPreferenceScreen() .findPreference(KEY_NOTIFICATION_RINGTONE)).setRingtonesPickListener(ringtonePickListener); ((DefaultRingtonePreference) getPreferenceScreen() .findPreference(KEY_ALARM_RINGTONE)).setRingtonesPickListener(ringtonePickListener); } //end
SoundSettings.java 在初始化 Preference 的时候,通过方法 setRingtonesPickListener 给 DefaultRingtonePreference.java 设置并实现了一个 RingtonePickListener,实现的 pick() 非常简单,就是调用了自身的 startActivityForResult() 并传入 REQUEST_RINGTONE_PICK 作为选择铃声的 REQUECT_CODE.
调用 startActivityForResult() 之后,逻辑会执行到 SoundSettings 的 onActivityResult(),最终走到 DefaultRingtonePreference 的 onActivityResult(),执行以下语句:
return super.onActivityResult(requestCode, resultCode, data);
最终执行到 DefaultRingtonePreference 的 onSaveRingtone() 方法,该方法通过 SubSelectSettings.java 返回来的数据判断应该给哪张 SIM 卡设置铃声,然后将其保存了起来。在 onSaveRingtone() 方法中,我们用到了 RingtoneManager.setActualDefaultRingtoneUriSim2() 方法,该方法是模仿 RingtoneManager.setActualDefaultRingtoneUri() 设计的,目的是给 SIM 卡二保存铃声。对应的还添加了 RingtoneManager.getActualDefaultRingtoneUriSim2() 方法。
RingtoneManager
接下来我们来看看 RingtoneManager.java 的修改。public class RingtoneManager { ...... /** @hide */ public static Uri getActualDefaultRingtoneUriSim2(Context context, int type) { String setting = getSettingForTypeSim2(type); if (setting == null) return null; String uriString = Settings.System.getStringForUser(context.getContentResolver(), setting, context.getUserId()); if(uriString == null) { uriString = Settings.System.getStringForUser(context.getContentResolver(), getSettingForType(type), context.getUserId()); setActualDefaultRingtoneUriSim2(context, type, Uri.parse(uriString)); } Log.i(TAG, "Get actual default ringtone uri= " + uriString); return uriString != null ? Uri.parse(uriString) : null; } private static String getSettingForTypeSim2(int type) { if ((type & TYPE_RINGTONE) != 0) { return Settings.System.RINGTONE_2; } else if ((type & TYPE_NOTIFICATION) != 0) { return Settings.System.NOTIFICATION_SOUND_2; } else if ((type & TYPE_ALARM) != 0) { return Settings.System.ALARM_ALERT_2; } else { return null; } } ...... }
可以看到,我们是通过 getSettingForTypeSim2 方法来判断铃声的类型的,在 getSettingForTypeSim2 方法中,我们模仿 getSettingForType 方法新加了 3 个常量用作存储 卡 2 铃声,这个比较简单,就不贴代码了,需要注意的是,public 权限的常量和方法需要加上 @hide 注解,否则就需要 update api 了。
Telecomm
最后我们来分析播放铃声的位置,位于packages\services\Telecomm\src\com\android\server\telecom\Ringer.java 的 startRinging() 方法中。
public class Ringer { ...... public void startRinging(Call foregroundCall) { //add by chenmj Uri defaultUri = null; List<SubscriptionInfo> subInfoList = SubscriptionManager.from(mContext) .getActiveSubscriptionInfoList(); android.util.Log.e("MCJ","subInfoList.size() : " + subInfoList.size()); if (subInfoList != null) { PhoneAccountHandle phoneAccountHandle = foregroundCall.getTargetPhoneAccount(); int subId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; int slotId = -1; if (phoneAccountHandle != null) { TelephonyManager tem = (TelephonyManager) mContext .getSystemService(Context.TELEPHONY_SERVICE); TelecomManager tm = (TelecomManager)mContext .getSystemService(Context.TELECOM_SERVICE); if (tem != null && tm != null) { try { PhoneAccount account = tm.getPhoneAccount(phoneAccountHandle); if (account != null) { subId = tem.getSubIdForPhoneAccount(account); } } catch(Exception e) { e.printStackTrace(); } } } if(subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { for(SubscriptionInfo subscriptionInfo : subInfoList) { if(subId == subscriptionInfo.getSubscriptionId()) { slotId = subscriptionInfo.getSimSlotIndex(); } } } if(slotId == 0) { defaultUri = RingtoneManager.getActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE); }else if(slotId == 1){ defaultUri = RingtoneManager.getActualDefaultRingtoneUriSim2(mContext, RingtoneManager.TYPE_RINGTONE); } android.util.Log.e("MCJ","defaultUri: " + defaultUri); if(defaultUri != null) { foregroundCall.setRingtone(defaultUri); } } //end mRingtonePlayer.play(mRingtoneFactory, foregroundCall); ...... } ......
上面的代码有点长,但是思路很简单,就是通过 TelephonyManager 实例的 getSubIdForPhoneAccount() 方法获取到 subId,然后根据这个 ID 来获取到 slotId,判断响铃的是卡一还是卡二,并据此调用 RingtoneManager 的对应的方法获取铃声 Uri,并通过方法 foregroundCall.setRingtone(defaultUri) 进行设置。setRingtone 方法是一个新加的方法,目的就是动态设置播放的铃声。需要修改
packages\services\Telecomm\src\com\android\server\telecom\Ringer.java,如下:
public class Call implements CreateConnectionResponse { ...... //add by chenmj void setRingtone(Uri uri) { if(uri != null) { mCallerInfo.contactRingtoneUri = uri; } } //end ......
这样一来,整个功能就完成了,如有疑问,欢迎留言,谢谢。
相关文章推荐
- android Music 中如何添加设置双卡铃声的菜单
- android Music 中如何添加设置双卡铃声的菜单
- Android平台,如何调用javascript操作网页和js调用系统功能
- 如何在Android平板电脑POWER按钮菜单中添加休眠功能
- Android系统移植与调试之------->如何修改Android设备添加重启、飞行模式、静音模式等功能(一)
- Android平台,如何调用javascript操作网页和js调用系统功能
- MTK 平台上如何给 camera 添加一种 preview size
- android 中如何添加新的键值,实现更多功能
- Android KitKat 4.4平台开发-添加USB ADB和MTP功能支持
- MTK android平台添加读写i2c设备工具
- MTK android 4.2.2添加重启功能
- MTK 平台上如何给 camera 添加一种 preview size
- MTK 平台上如何给 camera 添加一种 preview size
- MTK 平台上如何给 camera 添加一种 preview size
- MTKAndroid4.2.2添加重启功能
- android源码开发 MTK 平台下获取双卡双待SIM卡信息
- Android 新工作,如何把website数据和功能转移到Android平台
- Android 普通APP APK 如何确认系统是MTK 平台
- Android平台,如何调用javascript操作网页和js调用系统功能
- 如何在Android平板电脑POWER按钮菜单中添加休眠功能