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

在 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) {

        //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

    ......

这样一来,整个功能就完成了,如有疑问,欢迎留言,谢谢。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值