最近做项目时,测试提出的一个问题,在解的过程中遇到过问题,现在已解决,特此记录一下。
思路:设置为SD卡上的铃声之后,保存这个铃声的路径,监听SD卡的插拔事件,当SD卡mount上并扫描完文件之后,再去数据库中查保存起来的路径,根据路径找到当前的uri,也就是新的content://media/external/audio/media/+数字
第一步:既然设置铃声为SDcard铃声,那么首先就需要写一个广播来监听T卡的插拔,也就是SDcard的卸载与安装了,代码如下:
/**
*
* function: update the calendar ringtone when external sdcard mounted or boot complete
*/
package com.android.calendar;
import java.io.File;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
public class CalendarRingtoneUpdateReceiver extends BroadcastReceiver{
private static final String TAG = "CalendarRingtoneExternalReceiver";
private Context mContext;
private static final int CAL_CHECK = 0;
private static final int CAL_QUIT = 1;
@Override
public void onReceive(Context arg0, Intent arg1) {
// TODO Auto-generated method stub
String action = arg1.getAction();
Log.d(TAG, "[onReceive] action: "+action);
if(TextUtils.isEmpty(action)) {
Log.w(TAG, "[onReceive] action is empty! just return.");
return;
}
if(action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
mContext = arg0;
HandlerThread thread = new HandlerThread("CalendarRingtoneExternalReceiver");
thread.start();
new CheckExternalHandler(thread.getLooper()).sendEmptyMessage(CAL_CHECK);
}
}
private final class CheckExternalHandler extends Handler {
public CheckExternalHandler(Looper looper) {
// TODO Auto-generated constructor stub
super(looper);
}
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
switch (msg.what) {
case CAL_CHECK:
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
String ringtoneUriForExternal = null;
Cursor cursor = null;
String soundUri = Utils.getRingTonePreference(mContext);
Log.d(TAG, "[onReceive] soundUri : "+soundUri);
if(TextUtils.isEmpty(soundUri)
|| !soundUri.startsWith(GeneralPreferences.NOTIFICATION_RINTONE_PATH_PREFIX)) {
Log.w(TAG, "soundUri is empty, just continue here.");
this.sendEmptyMessage(CAL_QUIT);
break;
}
ringtoneUriForExternal = preferences.getString(GeneralPreferences.EXTERNAL_RINGTONE_PATH, null);
Log.d(TAG, "[onReceive] soundUriForExternal : "+ringtoneUriForExternal);
if(TextUtils.isEmpty(ringtoneUriForExternal)) {
Log.w(TAG, "soundUriForExternal is empty, we didn't save the external path before.");
this.sendEmptyMessage(CAL_QUIT);
break;
}
//get the real path and check if the file exists
cursor = mContext.getContentResolver().query(Uri.parse(soundUri), new String[]{ MediaStore.Audio.Media.DATA }, null, null, null);
try {
if(cursor != null && cursor.getCount() > 0) {
if(cursor.moveToFirst()) {
String filePath = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
Log.d(TAG, "[onReceive] filePath : " + filePath);
File ringtoneFile = new File(filePath);
if(ringtoneFile.exists() && ringtoneFile.isFile()) {
Log.d(TAG, "The ringtone file " + filePath +" exists, just continue here.");
Log.d(TAG, "[onReceive] filePath : " + filePath);
this.sendEmptyMessage(CAL_QUIT);
break;
}
}
if(cursor != null) {
cursor.close();
}
}
cursor = GeneralPreferences.getDataFromUri(mContext, ringtoneUriForExternal);
if(cursor != null && cursor.getCount() > 0) {
if(cursor.moveToFirst()) {
soundUri = GeneralPreferences.NOTIFICATION_RINTONE_PATH_PREFIX + cursor.getInt(0);
Log.d(TAG, "[onReceive] soundUri : " + soundUri);
Utils.setRingTonePreference(mContext, soundUri);
}
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
} finally {
if(cursor != null) {
cursor.close();
}
}
this.sendEmptyMessage(CAL_QUIT);
break;
case CAL_QUIT:
Log.d(TAG, "Processing done, quit now.");
this.getLooper().quit();
break;
default:
break;
}
}
}
}
GeneralPreferences.java;
public static final String MEDIA_EXTERNAL_PATH = "content://media/external/";
public static final String NOTIFICATION_RINTONE_PATH_PREFIX = "content://media/external/audio/media/";
public static final String EXTERNAL_RINGTONE_PATH = "external_ringtone_path";
public static final String[] MEDIA_COLUMNS = new String[] {
MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE,
"\"" + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "\"",
MediaStore.Audio.Media.TITLE_KEY
};
public static Cursor getDataFromUri(Context context, String soundUri) {
StringBuilder builder = new StringBuilder();
builder.append(MediaStore.Audio.Media.TITLE + " != ''");
builder.append(" AND " + MediaStore.Audio.Media.DATA + " = " + "\"" + soundUri + "\"");
builder.append(" AND " + MediaStore.Audio.Media.IS_MUSIC + "=1");
builder.append(" AND mime_type!='audio/x-ms-wma'");
return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
GeneralPreferences.MEDIA_COLUMNS, builder.toString(), null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
}
第二步:写了广播,那么就需要在AndroidManifest.xml中注册了,如下:
<receiver android:name="com.android.calendar.CalendarRingtoneUpdateReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_MOUNTED" />
<action android:name="android.intent.action.MEDIA_UNMOUNTED" />
<data android:scheme="file"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_SCANNER_FINISHED" />
<data android:scheme="file" />
</intent-filter>
</receiver>
注册时,多了一个扫描文件完成的action,这个是media在扫描完文件后发的广播,T卡重新remount之后,media数据库会重新建立。此时广播的工作已经完成啦。
第三步:开始初始化的工作,就需要在初始化的时候,做判断了:
GeneralPreferences.java 的 onCreate():
String ringToneUri = Utils.getRingTonePreference(activity);
String ringtoneDisplayUri = getRingtoneDisplayUri(ringToneUri);
editor.putString(GeneralPreferences.KEY_ALERTS_RINGTONE, ringtoneDisplayUri).apply();
//Here is not saved, otherwise you can not uninstall the sdcard card, then install sdcard, can not be restored sdcard ringtones
//此处不能保存,因为当设置为sdcard铃声后,卸载sdcard变为默认铃声后,再安装sdcard,会不能恢复sdcard上的铃声
//Utils.setRingTonePreference(activity, ringtoneDisplayUri);
String ringtoneDisplayString = getRingtoneTitleFromUri(activity, ringtoneDisplayUri);
当从Utils.getRingTonePreferences()获取铃声之后,需要对获取到的铃声做判断了:
private String getRingtoneDisplayUri(String ringToneUri) {
String soundValue = ringToneUri;
if(soundValue != null) {
if(soundValue.startsWith(GeneralPreferences.MEDIA_EXTERNAL_PATH)) {
try {
Cursor cursor = getActivity().getContentResolver().query(Uri.parse(soundValue), new String[] {MediaStore.Audio.Media.DATA}, null, null, null);
if(cursor == null || cursor.getCount() < 1) {
soundValue = getNewRingtoneUriFromBackupFilePath();
} else {
if(cursor.moveToFirst()) {
String filePath = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
File ringtoneFile = new File(filePath);
if(!ringtoneFile.exists() || !ringtoneFile.isFile()) {
soundValue = getNewRingtoneUriFromBackupFilePath();
}
} else {
soundValue = GeneralPreferences.DEFAULT_RINGTONE;
}
}
cursor.close();
} catch (Exception e) {
// TODO: handle exception
soundValue = GeneralPreferences.DEFAULT_RINGTONE;
}
}
}
return soundValue;
}
private String getNewRingtoneUriFromBackupFilePath() {
Cursor cursor = null;
String soundValue = null;
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
String soundUriForExternal = preferences.getString(GeneralPreferences.EXTERNAL_RINGTONE_PATH, null);
if(!TextUtils.isEmpty(soundUriForExternal)) {
cursor = getDataFromUri(getActivity(), soundUriForExternal);
if(cursor == null || cursor.getCount() < 1) {
soundValue = GeneralPreferences.DEFAULT_RINGTONE;
} else {
try {
if(cursor.moveToFirst()) {
soundValue = GeneralPreferences.NOTIFICATION_RINTONE_PATH_PREFIX + cursor.getInt(0);
Utils.setRingTonePreference(getActivity(), soundValue);
}
} catch (Exception e) {
// TODO: handle exception
soundValue = GeneralPreferences.DEFAULT_RINGTONE;
e.printStackTrace();
} finally {
cursor.close();
}
}
if(cursor != null) {
cursor.close();
}
} else {
soundValue = GeneralPreferences.DEFAULT_RINGTONE;
}
return soundValue;
}
此时,初始化的工作已经完成了。
第四步:需要在选择铃声事件时,如果是设置的sdcard铃声,需要保存一下:
GeneralPreferences.java 的 onPreferenceChange()方法中,当 preference == mRingtone 时,添加:
if(((String) newValue).startsWith(GeneralPreferences.MEDIA_EXTERNAL_PATH)) {
saveRingtoneInfoForExternal(activity, (String) newValue);
} else {
removeRingtoneInfoForExternal(activity);
}
private void saveRingtoneInfoForExternal(Context context, String ringtoneUri) {
String data = null;
try {
Cursor cursor = context.getContentResolver().query(Uri.parse(ringtoneUri),
new String[] { MediaStore.Audio.Media.DATA }, null, null, null);
if(cursor != null && cursor.getCount() > 0) {
if(cursor.moveToFirst()) {
data = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
}
}
cursor.close();
} catch (Exception e) {
// TODO: handle exception
}
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
preferences.edit().putString(GeneralPreferences.EXTERNAL_RINGTONE_PATH, data).commit();
}
private void removeRingtoneInfoForExternal(Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
preferences.edit().remove(GeneralPreferences.EXTERNAL_RINGTONE_PATH).commit();
}
至此,选择铃声事件的工作也已经完成了。这时,会发现一个问题,那么就是:
设置为sdcard铃声后,卸载sdcard,设置-常规设置中,通知的铃声,已经变为默认铃声,而且点击进去,也勾选了默认铃声,可是,来日程事件时,并没有任何铃声输出。
问题在于:此时SharePreferences中的铃声,并没有修改,但是SharedPreferences中的铃声并不能修改;因为修改后,卸载sdcard后,安装sdcard后,会无法恢复sdcard铃声。
那么该如何修改呢,后来发现原来是在 初始化Notification 时,获取的铃声文件还是sdcard铃声,此时就需要再次判断。
第五步:修改 Notification 的铃声,如下:
AlertService.java 中的 updateAlertNotification() --> generateAlerts() :
// Add options for a quiet update.
addNotificationOptions(notification, true, expiredDigestTitle,
notificationPrefs.getDefaultVibrate(),
notificationPrefs.getRingtoneAndSilence(context),
false); /* Do not show the LED for the expired events. */
此时可以选择修改addNotificationOptions() 中的:
// Possibly generate a sound. If 'Silent' is chosen, the ringtone
// string will be empty.
notification.sound = TextUtils.isEmpty(reminderRingtone) ? null : Uri
.parse(reminderRingtone);
也可以选择修改 getRingtoneAndSilence(),我选择的是后者,如下:
private String getRingtoneAndSilence(Context context) {
if (ringtone == null) {
if (quietUpdate) {
ringtone = EMPTY_RINGTONE;
} else {
ringtone = Utils.getRingTonePreference(context);
if(!TextUtils.isEmpty(ringtone) && ringtone.startsWith(GeneralPreferences.MEDIA_EXTERNAL_PATH)) {
Cursor cursor = context.getContentResolver().query(Uri.parse(ringtone), null, null, null, null);
if(cursor == null || cursor.getCount() < 1) {
ringtone = GeneralPreferences.DEFAULT_RINGTONE;
}
if(cursor != null) {
cursor.close();
}
}
}
}
String retVal = ringtone;
ringtone = EMPTY_RINGTONE;
return retVal;
}
至此,大功告成啦。