系统默认来电铃声
作者: wangqx 日 期: 2016.6.1 QQ群 238696947
修订记录
日期 修订版本 描述 作者
2016/6/1 1.0 系统设置默认铃声显示
一、简介
本文内容以xxx(PROJECTXXX)项目,Android6.0为例,主要介绍默认铃声从在mk文件中添加ro.config.ringtone字段到系统设置中声音和振动项中手机铃声的显示。
二、mk文件介绍
1、文件修改
修改默认铃声(以下默认铃声均指来电默认铃声)的 mk文件中所在路径:
Z:\PROJECTXXX\android\device\qcom\Xxxx70\Xxxx70.mk(推荐)
Z:\PROJECTXXX\android\device\qcom\Xxxx70\hipad_config\hipad_config.mk
Z:\PROJECTXXX\android\device\qcom\Xxxx70\hipad_config\hipad_prop.mk
添加变量
PRODUCT_PROPERTY_OVERRIDES += \
ro.config.ringtone=Fantastic_Clang.ogg \
ro.config.ringtone_2=Jocosity.ogg \
ro.config.notification_sound=Notice.ogg \
ro.config.notification_sound_2=Curiosity.ogg \
ro.config.notification_sound_3=Curiosity.ogg \
ro.config.alarm_alert=New_Morning.ogg \
persist.sys.note_op_enable = true
2、注意事项及疑惑解答
注:以上路径Xxxx70为以项目为名称的文件夹,主要为了辨识。例:xxx项目在编译时选择项目时大家会选择21,即Xxxx70,mypro项目编译时选择项目名为M6xx,android\device\qcom\目录下会有与项目名对应的文件夹(文件夹名根据OEM厂商自定配置)。
mk文件主要记录当前要编译出的产品的信息,例版本号,项目名,基线,等设备配置信息及默认配置如默认铃声,默认数据流量开关,默认语言,默认输入法等软件配置信息。源码编译完成后会生成build.prop文件
Z:\PROJECTXXX\android\out\target\product\msm8909\system\build.prop,此文件内容为项目最终使用的默认配置信息。
小编同时在
Z:\PROJECTXXX\android\device\qcom\Xxxx70\Xxxx70.mk
Z:\PROJECTXXX\android\device\qcom\Xxxx70\hipad_config\ hipad_config.mk
Z:\PROJECTXXX\android\device\qcom\Xxxx70\hipad_config\ hipad_prop.mk
文件中设置不同的默认铃声(为ro.config.ringtone赋不同的铃声资源值),最终在build.prop文件中生成的是
Z:\PROJECTXXX\android\device\qcom\Xxxx70\Xxxx70.mk文件中的默认铃声信息,
故推荐在此文件中添加默认铃声信息。
注:并不是所有的配置信息都在此文件中添加而防止被覆盖,默认铃声之所以推荐在此文件中添加,是因为其他未知地方可能添加了默认铃声配置的信息,不同的参数在何处的mk文件中添加需开发自行斟酌。
注:mk文件中内容默认不允许有空格、换行符,换行时需添加“\”。
变量名PRODUCT_PROPERTY_OVERRIDES的由来,能不能换成其他的变量名
此变量名在Z:\PROJECTXXX\android\build\core\ product.mk中定义,是_product_var_list 数组的一个成员(小编这样理解),只需和此处对应即可。
至于为何写成ro.config.ringtone问题:
ro.config.ringtone中的ringtone能换成其他名称,只需与代码中使用处保持一致即可,ro.config.xxxx 前缀ro.config小编只知道此种写法涉及到android中权限问题,其他不知。
铃声名称Jocosity.ogg要与Z:\PROJECTXXX\android\frameworks\base\data\sounds\ringtones
来电铃声目录下的资源名相对应,Z:\PROJECTXXX\android\frameworks\base\data\sounds\目录为手机系统中使用的铃声资源,在编译过程中,会根据
AllAudio.mk文件将铃声资源copy到
Z:\PROJECTXXX\android\out\target\product\msm8909\system\media\audio目录,在手机中的目录为system\media\
三、默认铃声uri生成
本节主要讲系统扫描文件,将默认铃声资源文件对应的uri写入数据库
1、关键方法:
setDefaultRingtoneFileNames()
endFile()
setSettingIfNotSet()
2、方法详细
Z:\PROJECTXXX\android\frameworks\base\media\java\android\media\MediaScanner.java中
private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config.";
/** Whether the scanner has set a default sound for the ringer ringtone. */
private boolean mDefaultRingtoneSet;
private boolean mDefaultRingtoneSet2;
/** The filename for the default sound for the ringer ringtone. */
private String mDefaultRingtoneFilename;
private String mDefaultRingtoneFilename2;
…
(1)setDefaultRingtoneFileNames
private void setDefaultRingtoneFileNames() {
mDefaultRingtoneFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
+ Settings.System.RINGTONE);//获取铃声文件名并赋值给mDefaultRingtoneFilename变量
mDefaultRingtoneFilename2 = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
+ Settings.System.RINGTONE_2);
…
}
注:ro.config.ringtone在代码中使用时是分开的,小编在看流程时通过命令
grep –C 1 ‘ro.config.ringtone’ . –include=”*.java” -rn
没有搜索到结果,最后仔细想了下,有可能是分开了,花费了好长时间才找到此处。
(2)endFile
SystemProperties.get()方法主要用来获取系统属性
FileEntry类中包含了文件的一些信息,如:格式,修改时间,路径,id等。
private Uri endFile(FileEntry entry, boolean ringtones, boolean notifications, boolean alarms, boolean music, boolean podcasts)
throws RemoteException {
…
if (rowId == 0) {
…
if (mWasEmptyPriorToScan) {
if (ringtones) {//扫描时,如果此文件是铃声资源文件
if ((TextUtils.isEmpty(mDefaultRingtoneFilename) ||
doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) && !mDefaultRingtoneSet) {
needToSetSettings = true;//置标示,需要设置
}
if ((TextUtils.isEmpty(mDefaultRingtoneFilename2) ||
doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename2)) && !mDefaultRingtoneSet2) {
needToSetSettings = true;
}
}
}
}
…
if(needToSetSettings) {
…
if (ringtones) {//扫描时,如果此文件是铃声资源文件
if (!mDefaultRingtoneSet && doesPathHaveFilename(entry.mPath,mDefaultRingtoneFilename)) {
setSettingIfNotSet(Settings.System.RINGTONE, tableUri,rowId);//将卡1铃声的uri写入数据库
/* Settings.System.RINGTONE 为settings.db数据库下system表中对应的ringtone字段*/
/* tableUri 为扫描此文件后,此文件夹对应的uri,rowid为此文件夹下此文件对应的id,每一个文件分别对应一个rowId */
setSettingIfNotSet(Settings.System.DEFAULT_RINGTONE,tableUri, rowId);
mDefaultRingtoneSet = true; //标识默认铃声及卡1铃声Uri已经写入数据库
}
if (!mDefaultRingtoneSet2 && doesPathHaveFilename(entry.mPath,mDefaultRingtoneFilename2)) {
setSettingIfNotSet(Settings.System.RINGTONE_2,tableUri, rowId);
/* Settings.System.RINGTONE 为settings.db数据库下system表中对应的ringtone_2字段*/
mDefaultRingtoneSet2 = true;//标识卡2铃声Uri已经写入数据库
}
}
}
}
(3)setSettingIfNotSet
private void setSettingIfNotSet(String settingName, Uri uri, long rowId) {
String existingSettingValue = Settings.System.getString(mContext.getContentResolver(),settingName);
if (TextUtils.isEmpty(existingSettingValue)) {
// Set the setting to the given URI
//putString为将ContentUris.withAppendedId(uri, rowId).toString()的值写入到数据库中的settingName字段
Settings.System.putString(mContext.getContentResolver(), settingName, ContentUris.withAppendedId(uri, rowId).toString());
}
}
经过以上处理,default_ringtone、ringtone 、ringtone_2所对应的uri已分别写入settings.db数据库下,system表中。
之所以添加default_ringtone字段是为了处理当卡1、卡2设置的自定义铃声资源被删除后,卡1、卡2的来电铃声能根据default_ringtone字段的值恢复到默认铃声。
四、设置中显示
烧机后或恢复出厂设置后,第一次开机
1、关键方法:
lookupRingtoneNames()
updateRingtoneNameBySim()
getActualRingtoneUriBySubId()
getStaticDefaultRingtoneUriBySubId()
2、方法详细
(1) lookupRingtoneNames
Z:\PROJECTXXX\android\packages\apps\Settings\src\com\android\settings\notification\ NotificationSettings.java
用户点击设置,进入声音和振动选项时,执行Notification中的
public void onResume() {
…
lookupRingtoneNames();
…
}
private void lookupRingtoneNames() {
AsyncTask.execute(mLookupRingtoneNames);//异步执行mLookupRingtoneNames接口中的run方法
}
private final Runnable mLookupRingtoneNames = new Runnable() {
@Override
public void run() {
if (mMultiSimRingtonePreference_sim1 != null) {
final CharSequence summary = updateRingtoneNameBySim(mContext, RingtoneManager.TYPE_RINGTONE,0);
// RingtoneManager.TYPE_RINGTONE为铃声类型
//0为subId
if (summary != null) {
mHandler.obtainMessage(H.UPDATE_PHONE_RINGTONE_SIM1, summary).sendToTarget();
//使用handler机制异步跟新,向目标线程发送UPDATE_PHONE_RINGTONE_SIM1消息
}
}
if (mMultiSimRingtonePreference_sim2 != null) {
// updateRingtoneNameBySim根据subid获取铃声名称字串
final CharSequence summary = updateRingtoneNameBySim(mContext, RingtoneManager.TYPE_RINGTONE,1);
if (summary != null) {
mHandler.obtainMessage(H.UPDATE_PHONE_RINGTONE_SIM2, summary).sendToTarget();
//获取铃声字串后,使用handler机制异步跟新,向目标线程发送UPDATE_PHONE_RINGTONE_SIM2消息
}
}
}
};
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
…
case UPDATE_PHONE_RINGTONE_SIM1:
//主线程接收到消息后设置铃声显示名称
mMultiSimRingtonePreference_sim1.setSummary((CharSequence) msg.obj);
break;
case UPDATE_PHONE_RINGTONE_SIM2:
mMultiSimRingtonePreference_sim2.setSummary((CharSequence) msg.obj);
break;
}
}
(2) updateRingtoneNameBySim
Z:\PROJECTXXX\android\packages\apps\Settings\src\com\android\settings\notification\ NotificationSettings.java
private static CharSequence updateRingtoneNameBySim(Context context, int type,int SubId) {
if (context == null) {
Log.e(TAG, "Unable to update ringtone name, no context provided");
return null;
}
Uri ringtoneUri = RingtoneManager.getActualRingtoneUriBySubId(context, SubId);
//关键方法,通过subId获取铃声的uri,此默认铃声uri已在MediaScanner.java类endFile()中写入数据库
CharSequence summary = context.getString(com.android.internal.R.string.ringtone_unknown);
// Is it a silent ringtone?
if (ringtoneUri == null) {
summary = context.getString(com.android.internal.R.string.ringtone_silent);
} else {
Cursor cursor = null;
try {
//通过ringtoneUri 获取此ringtoneUri对应的铃声名称
if (MediaStore.AUTHORITY.equals(ringtoneUri.getAuthority())) {
// Fetch the ringtone title from the media provider
cursor = context.getContentResolver().query(ringtoneUri, new String[] { MediaStore.Audio.Media.TITLE }, null, null, null);
} else if (ContentResolver.SCHEME_CONTENT.equals(ringtoneUri.getScheme())) {
cursor = context.getContentResolver().query(ringtoneUri, new String[] { OpenableColumns.DISPLAY_NAME }, null, null, null);
}
if (cursor != null) {
if (cursor.moveToFirst()) {
summary = cursor.getString(0);
}
}
} catch (SQLiteException sqle) {
// Unknown title for the ringtone
} catch (IllegalArgumentException iae) {
// Some other error retrieving the column from the provider
} finally {
if (cursor != null) {
cursor.close();
}
}
}
return summary;//返回默认铃声名称
}
(3) getActualRingtoneUriBySubId
Z:\PROJECTXXX\android\frameworks\base\media\java\android\media\RingtoneManager.java
public static Uri getActualRingtoneUriBySubId(Context context, int subId) {
if (!(subId >= 0 && subId < Settings.System.MAX_NUM_RINGTONES)) {
return null;
}
String setting;
//通过subId判断是卡1还是卡x铃声
if (subId == 0) {
//subId == 0对应卡1铃声
setting = Settings.System.RINGTONE;//ringtone
} else {
//ringtone_x,subId ==1对应卡2铃声,卡2铃声字段为ringtone_2
setting = Settings.System.RINGTONE + "_" + (subId + 1);
}
//getString为获取setting字串ringtone或ringtone_x
final String uriString = Settings.System.getString(context.getContentResolver(), setting);
if (uriString == null) {
return null;
}
Uri ringToneUri = getStaticDefaultRingtoneUriBySubId(context,subId);//通过传入的subId获取默认的铃声uri
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(Uri.parse(uriString), null, null, null, null);
if ((cursor != null) && (cursor.getCount() > 0)) {
ringToneUri = Uri.parse(uriString);
}
} catch (SQLiteException ex) {
Log.e(TAG, "ex " + ex);
} finally {
if (cursor != null) cursor.close();
}
return ringToneUri;
}
(4) getStaticDefaultRingtoneUriBySubId
Z:\PROJECTXXX\android\frameworks\base\media\java\android\media\RingtoneManager.java
public static Uri getStaticDefaultRingtoneUriBySubId(Context context,int subId) {
if (!(subId >= 0 && subId < Settings.System.MAX_NUM_RINGTONES)) {
return null;
}
String setting;
if (subId == 0) {
//getString为获取Settings.System.DEFAULT_RINGTONE.toString()字段在数据库中的值
//对应MediaScanner.java中的setSettingIfNotSet方法中的putString
final String uriString_def =
Settings.System.getString(context.getContentResolver(), Settings.System.DEFAULT_RINGTONE.toString());
return uriString_def != null ? Uri.parse(uriString_def) : null;
} else {
final String uriString =
Settings.System.DEFAULT_RINGTONE.toString() + "_" + (subId + 1);
return uriString != null ? Uri.parse(uriString) : null;
}
}