最近在做一个小feature,要求用户在设置来电铃音、信息提示音、闹钟铃音、日历提示音时可以浏览本地音频文件(内置或外置SD卡上),并能将本地音频文件设置为相应的铃音或者提示音等。
经过分析发现,系统默认的铃音、提示音等都是直接调用RingtonePreference,点击响应的处理在其onClick()方法中,点击该preference后弹出铃音列表RingtonePickerActivity.java,选中某一铃音点击确定后,将所选结果传回RingtonePreference.java。在RingtonePreference.java的onActivityResult()方法中将设置结果保存并刷新界面。界面展示如下图所示:
因此,如果要求在来电铃音、信息提示音、闹钟铃音、日历提示音时都可以让用户选择浏览本地音频文件或者系统铃音,而这些地方都是直接用的RingtonePreference,那么只有修改RingtonePreference的onClick()方法,即点击preference后弹出一个对话框让用户选择是浏览本地音频文件还是系统铃音。在此,浏览本地文件我们是调用的Music应用的MusicPicker界面,选择本地音频后将结果返回给RingtonePreference。先将完成后的结果展示如下:
要达到上面的效果,并且支持Calendar、Message、AlarmClock关于提示音或铃音的设置,基于android4.2.2源码,需要修改的文件有:
1、frameworks/base/core/java/android/preferences/RingtonePreference.java
2、frameworks/base/core/res/res/values/strings.xml
3、frameworks/base/core/res/res/values/public.xml
4、packages/apps/Calendar/AndroidManifest.xml 增加权限 "android.permission.WRITE_EXTERNAL_STORAGE"
5、packages/apps/Calendar/src/com/android/calendar/GeneralPreferences.java
6、packages/apps/Music/src/com/android/music/MusicPicker.java
7、packages/providers/MediaProvider/src/com/android/providers/media/RingtonePickerActivity.java
8、packages/apps/DeskClock/src/com/android/deskclock/AlarmClock.java
9、packages/apps/DeskClock/res/values/strings.xml
具体修改如下(改动较多的就直接贴上整个文件,改动较小的只给出改动部分):
1、frameworks/base/core/java/android/preferences/RingtonePreference.java
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.preference;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.app.Activity;
import android.media.RingtoneManager;
import android.net.Uri;
import android.provider.Settings.System;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.app.AlertDialog;
import android.content.ContentValues;
import android.provider.MediaStore;
import java.util.ArrayList;
import android.R;
/**
* A {@link Preference} that allows the user to choose a ringtone from those on the device.
* The chosen ringtone's URI will be persisted as a string.
*
* If the user chooses the "Default" item, the saved string will be one of
* {@link System#DEFAULT_RINGTONE_URI},
* {@link System#DEFAULT_NOTIFICATION_URI}, or
* {@link System#DEFAULT_ALARM_ALERT_URI}. If the user chooses the "Silent"
* item, the saved string will be an empty string.
*
* @attr ref android.R.styleable#RingtonePreference_ringtoneType
* @attr ref android.R.styleable#RingtonePreference_showDefault
* @attr ref android.R.styleable#RingtonePreference_showSilent
*/
public class RingtonePreference extends Preference implements
PreferenceManager.OnActivityResultListener {
private static final String TAG = "RingtonePreference";
//added begin
private static final int MUSIC_PICKER_REQUEST_CODE = 10;
private static final String RINGTONE_TYPE = "ringtone_type";
//added end
private int mRingtoneType;
private boolean mShowDefault;
private boolean mShowSilent;
private int mRequestCode;
public RingtonePreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.RingtonePreference, defStyle, 0);
mRingtoneType = a.getInt(com.android.internal.R.styleable.RingtonePreference_ringtoneType,
RingtoneManager.TYPE_RINGTONE);
mShowDefault = a.getBoolean(com.android.internal.R.styleable.RingtonePreference_showDefault,
true);
mShowSilent = a.getBoolean(com.android.internal.R.styleable.RingtonePreference_showSilent,
true);
a.recycle();
}
public RingtonePreference(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.ringtonePreferenceStyle);
}
public RingtonePreference(Context context) {
this(context, null);
}
/**
* Returns the sound type(s) that are shown in the picker.
*
* @return The sound type(s) that are shown in the picker.
* @see #setRingtoneType(int)
*/
public int getRingtoneType() {
return mRingtoneType;
}
/**
* Sets the sound type(s) that are shown in the picker.
*
* @param type The sound type(s) that are shown in the picker.
* @see RingtoneManager#EXTRA_RINGTONE_TYPE
*/
public void setRingtoneType(int type) {
mRingtoneType = type;
}
/**
* Returns whether to a show an item for the default sound/ringtone.
*
* @return Whether to show an item for the default sound/ringtone.
*/
public boolean getShowDefault() {
return mShowDefault;
}
/**
* Sets whether to show an item for the default sound/ringtone. The default
* to use will be deduced from the sound type(s) being shown.
*
* @param showDefault Whether to show the default or not.
* @see RingtoneManager#EXTRA_RINGTONE_SHOW_DEFAULT
*/
public void setShowDefault(boolean showDefault) {
mShowDefault = showDefault;
}
/**
* Returns whether to a show an item for 'Silent'.
*
* @return Whether to show an item for 'Silent'.
*/
public boolean getShowSilent() {
return mShowSilent;
}
/**
* Sets whether to show an item for 'Silent'.
*
* @param showSilent Whether to show 'Silent'.
* @see RingtoneManager#EXTRA_RINGTONE_SHOW_SILENT
*/
public void setShowSilent(boolean showSilent) {
mShowSilent = showSilent;
}
@Override
protected void onClick() {
//modified begin
AlertDialog.Builder builder = new AlertDialog.Builder(getPreferenceManager().getContext());
ArrayList
arrayList.add(getContext().getString(android.R.string.moto_ringtone_preference_choose_action_ringtone));
arrayList.add(getContext().getString(android.R.string.moto_ringtone_preference_choose_action_music_player));
CharSequence[] list = arrayList.toArray(new String[arrayList.size()]);
builder.setTitle(android.R.string.moto_ringtone_preference_choose_action);
builder.setItems(list, new RingtonePreferenceClickListner());
builder.setNegativeButton(getContext().getString(android.R.string.cancel), new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.create().show();
//modified end
}
/**
* added
*/
private class RingtonePreferenceClickListner implements OnClickListener {
@Override
public void onClick(DialogInterface dialog, int id) {
if (id == 1) {
// add what you sent intent here and add below parameter for
// music player picker, this will be your onClick()
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.putExtra(RINGTONE_TYPE, mRingtoneType);
intent.setType("audio/*");
PreferenceFragment owningFragment = getPreferenceManager()
.getFragment();
if (owningFragment != null) {
owningFragment.startActivityForResult(intent, MUSIC_PICKER_REQUEST_CODE);
} else {
getPreferenceManager().getActivity()
.startActivityForResult(intent, MUSIC_PICKER_REQUEST_CODE);
}
} else {
// add what you sent intent here for default ringtone picker,
// this will be your onClick()
Intent intent = new Intent(
RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RINGTONE_TYPE, mRingtoneType);
onPrepareRingtonePickerIntent(intent);
PreferenceFragment owningFragment = getPreferenceManager()
.getFragment();
if (owningFragment != null) {
owningFragment.startActivityForResult(intent, mRequestCode);
} else {
getPreferenceManager().getActivity()
.startActivityForResult(intent, mRequestCode);
}
}
dialog.dismiss();
}
}
/**
* Prepares the intent to launch the ringtone picker. This can be modified
* to adjust the parameters of the ringtone picker.
*
* @param ringtonePickerIntent The ringtone picker intent that can be
* modified by putting extras.
*/
protected void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) {
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI,
onRestoreRingtone());
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, mShowDefault);
if (mShowDefault) {
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
RingtoneManager.getDefaultUri(getRingtoneType()));
}
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, mShowSilent);
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, mRingtoneType);
ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, getTitle());
}
/**
* Called when a ringtone is chosen.
*
* By default, this saves the ringtone URI to the persistent storage as a
* string.
*
* @param ringtoneUri The chosen ringtone's {@link Uri}. Can be null.
*/
protected void onSaveRingtone(Uri ringtoneUri) {
persistString(ringtoneUri != null ? ringtoneUri.toString() : "");
}
/**
* Called when the chooser is about to be shown and the current ringtone
* should be marked. Can return null to not mark any ringtone.
*
* By default, this restores the previous ringtone URI from the persistent
* storage.
*
* @return The ringtone to be marked as the current ringtone.
*/
protected Uri onRestoreRingtone() {
final String uriString = getPersistedString(null);
return !TextUtils.isEmpty(uriString) ? Uri.parse(uriString) : null;
}
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getString(index);
}
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValueObj) {
String defaultValue = (String) defaultValueObj;
/*
* This method is normally to make sure the internal state and UI
* matches either the persisted value or the default value. Since we
* don't show the current value in the UI (until the dialog is opened)
* and we don't keep local state, if we are restoring the persisted
* value we don't need to do anything.
*/
if (restorePersistedValue) {
return;
}
// If we are setting to the default value, we should persist it.
if (!TextUtils.isEmpty(defaultValue)) {
onSaveRingtone(Uri.parse(defaultValue));
}
}
@Override
protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
super.onAttachedToHierarchy(preferenceManager);
preferenceManager.registerOnActivityResultListener(this);
mRequestCode = preferenceManager.getNextRequestCode();
}
public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
//modified begin
if(data != null){
int ringtoneType = data.getIntExtra(RINGTONE_TYPE, -1);
if(ringtoneType == mRingtoneType){
if (requestCode == mRequestCode){
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
if (callChangeListener(uri != null ? uri.toString() : "")) {
onSaveRingtone(uri);
}
return true;
}
else if(requestCode == MUSIC_PICKER_REQUEST_CODE){
Uri uri = data.getData();
if (callChangeListener(uri != null ? uri.toString() : "")) {
onSaveRingtone(uri);
updateItemInExternal(uri);
}
return true;
}
}
}
//modified end
return false;
}
/**
* added
*/
private void updateItemInExternal(Uri uri){
ContentValues values = new ContentValues();
String key = null;
if(mRingtoneType == RingtoneManager.TYPE_RINGTONE){
key = MediaStore.Audio.AudioColumns.IS_RINGTONE;
}
else if(mRingtoneType == RingtoneManager.TYPE_NOTIFICATION){
key = MediaStore.Audio.AudioColumns.IS_NOTIFICATION;
}
else if(mRingtoneType == RingtoneManager.TYPE_ALARM){
key = MediaStore.Audio.AudioColumns.IS_ALARM;
}
values.put(key, "1");
getPreferenceManager().getActivity().getContentResolver().update(uri, values, null, null);
}
}
2、frameworks/base/core/res/res/values/strings.xml
3、frameworks/base/core/res/res/values/public.xml
4、packages/apps/Calendar/AndroidManifest.xml 增加权限 "android.permission.WRITE_EXTERNAL_STORAGE"
5、packages/apps/Calendar/src/com/android/calendar/GeneralPreferences.java
@Override
public void onStop() {
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
mContentResolver.unregisterContentObserver(mObserver);
/** removed
* Calendar ringtonepreference summary don't update after change notification to local file.
setPreferenceListeners(null);
*/
super.onStop();
}
/**
* added
* Calendar ringtonepreference summary don't update after change notification to local file.
*/
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
setPreferenceListeners(null);
}
6、packages/apps/Music/src/com/android/music/MusicPicker.java
public class MusicPicker extends ListActivity
implements View.OnClickListener, MediaPlayer.OnCompletionListener,
MusicUtils.Defs {
static final boolean DBG = false;
static final String TAG = "MusicPicker";
//added begin
private static final String RINGTONE_TYPE = "ringtone_type";
private int mRingtoneType;
//added end
…………………………
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
int sortMode = TRACK_MENU;
//added begin
mRingtoneType = getIntent().getIntExtra(RINGTONE_TYPE, -1);
//added end
………………………
}
…………………………
public void onClick(View v) {
switch (v.getId()) {
case R.id.okayButton:
if (mSelectedId >= 0) {
//modified begin
Intent it = new Intent();
it.putExtra(RINGTONE_TYPE, mRingtoneType);
setResult(RESULT_OK, it.setData(mSelectedUri));
//modified end
finish();
}
break;
case R.id.cancelButton:
finish();
break;
}
}
}
7、packages/providers/MediaProvider/src/com/android/providers/media/RingtonePickerActivity.java
public final class RingtonePickerActivity extends AlertActivity implements
AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener,
AlertController.AlertParams.OnPrepareListViewListener {
private static final String TAG = "RingtonePickerActivity";
//added begin
private static final String RINGTONE_TYPE = "ringtone_type";
private int mRingtoneType;
//added end
……………………
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler();
Intent intent = getIntent();
/*
* Get whether to show the 'Default' item, and the URI to play when the
* default is clicked
*/
mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI);
//added begin
mRingtoneType = intent.getIntExtra(RINGTONE_TYPE, -1);
//added end
……………………
}
……………………
/*
* On click of Ok/Cancel buttons
*/
public void onClick(DialogInterface dialog, int which) {
boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE;
// Stop playing the previous ringtone
mRingtoneManager.stopPreviousRingtone();
if (positiveResult) {
Intent resultIntent = new Intent();
Uri uri = null;
if (mClickedPos == mDefaultRingtonePos) {
// Set it to the default Uri that they originally gave us
uri = mUriForDefaultItem;
} else if (mClickedPos == mSilentPos) {
// A null Uri is for the 'Silent' item
uri = null;
} else {
uri = mRingtoneManager.getRingtoneUri(getRingtoneManagerPosition(mClickedPos));
}
resultIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, uri);
//added begin
resultIntent.putExtra(RINGTONE_TYPE, mRingtoneType);
//added end
setResult(RESULT_OK, resultIntent);
} else {
setResult(RESULT_CANCELED);
}
getWindow().getDecorView().post(new Runnable() {
public void run() {
mCursor.deactivate();
}
});
finish();
}
}
8、packages/apps/DeskClock/src/com/android/deskclock/AlarmClock.java
public class AlarmClock extends Activity implements LoaderManager.LoaderCallbacks
AlarmTimePickerDialogFragment.AlarmTimePickerDialogHandler,
LabelDialogFragment.AlarmLabelDialogHandler,
OnLongClickListener, Callback, DialogInterface.OnClickListener {
……………………
//added begin
private static final int MUSIC_PICKER_REQUEST_CODE = 10;
private static final String RINGTONE_TYPE = "ringtone_type";
//added end
……………………
private void launchRingTonePicker(Alarm alarm) {
mSelectedAlarm = alarm;
// final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
// intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, alarm.alert);
// intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM);
// intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false);
// startActivityForResult(intent, REQUEST_CODE_RINGTONE);
//added begin
AlertDialog.Builder builder = new AlertDialog.Builder(this);
ArrayList
arrayList.add(getString(R.string.moto_ringtone_preference_choose_action_ringtone));
arrayList.add(getString(R.string.moto_ringtone_preference_choose_action_music_player));
CharSequence[] list = arrayList.toArray(new String[arrayList.size()]);
builder.setTitle(R.string.moto_ringtone_preference_choose_action);
builder.setItems(list, new RingtonePreferenceClickListner());
builder.setNegativeButton(getString(android.R.string.cancel), new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.create().show();
//added end
}
/**
* added
*/
private class RingtonePreferenceClickListner implements OnClickListener {
@Override
public void onClick(DialogInterface dialog, int id) {
if (id == 1) {
// add what you sent intent here and add below parameter for
// music player picker, this will be your onClick()
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.putExtra(RINGTONE_TYPE, RingtoneManager.TYPE_ALARM);
intent.setType("audio/*");
startActivityForResult(intent, MUSIC_PICKER_REQUEST_CODE);
} else {
// add what you sent intent here for default ringtone picker,
// this will be your onClick()
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RINGTONE_TYPE, RingtoneManager.TYPE_ALARM);
onPrepareRingtonePickerIntent(intent);
startActivityForResult(intent, REQUEST_CODE_RINGTONE);
}
dialog.dismiss();
}
}
/**
* added
*/
private void onPrepareRingtonePickerIntent(Intent intent) {
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, mSelectedAlarm.alert);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false);
}
/**
* modified
*/
private void saveRingtoneUri(final Uri uri) {
mSelectedAlarm.alert = uri;
// Save the last selected ringtone as the default for new alarms
RingtoneManager.setActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM, uri);
asyncUpdateAlarm(mSelectedAlarm, false);
}
……………………
}
9、packages/apps/DeskClock/res/values/strings.xml