您的位置:首页 > 移动开发 > Android开发

Android MTK N 平台上如何添加双卡铃声功能

2017-10-30 11:32 567 查看
在 MTK M 平台上,是自带双卡铃声功能,因此部分客户升级到 N 平台之后,也会有这样的要求。那么我们就来看看如何实现这一功能。

实现这功能主要从 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

......


这样一来,整个功能就完成了,如有疑问,欢迎留言,谢谢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息