I have this RingtonePreference (from Android Studio's default SettingsActivity):
pref_notification.xml:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<RingtonePreference
android:dependency="notifications_alarm"
android:key="notifications_alarm_ringtone"
android:title="@string/pref_title_ringtone"
android:ringtoneType="notification|all"
android:defaultValue="content://settings/system/notification_sound" />
SettingsActivity.java:
private void setupSimplePreferencesScreen() {
if (!isSimplePreferences(this)) {
return;
}
// Add 'general' preferences.
addPreferencesFromResource(R.xml.pref_general);
// Add 'notifications' preferences, and a corresponding header.
PreferenceCategory fakeHeader = new PreferenceCategory(this);
fakeHeader.setTitle(R.string.pref_header_notifications);
getPreferenceScreen().addPreference(fakeHeader);
addPreferencesFromResource(R.xml.pref_notification);
bindPreferenceSummaryToValue(findPreference("notifications_alarm_ringtone"));
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static class NotificationPreferenceFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_notification);
bindPreferenceSummaryToValue(findPreference("notifications_alarm_ringtone"));
}
}
I would like to add my app's custom ringtones from res/raw folder to the list. (I don't need them to be available for other apps.)
Option 1: Copy to device storage
Copy your ringtone files to the device's storage. I did basically this for an app on GitHub, here, where I copied alarm tones from raw resources to the device's alarms directory (you'll need to replace all the instances of "alarms" with "ringtones"). Then we use ContentValues
to create metadata telling the system that the files are ringtones and use MediaStore.Audio.Media.getContentUriForPath
and then context.getContentResolver().insert(contentUri, contentValues)
to add the ringtones to the device's database, so they'll be included in the RingtonePreference
's list.
You can also set a ringtone as the default using RingtoneManager.setActualDefaultRingtoneUri()
, though you'll need the WRITE_SETTINGS
permission.
Also, remember to use the URI you get from getContentUriForPath()
when calling getContentResolver().insert()
and RingtoneManager.setActualDefaultRingtoneUri()
.
And, make sure you add to your AndroidManifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Option 2: Custom preference
Create a custom preference, as shown in this guide, or you can use (or subclass) the ListPreference. You will need to retrieve all the device's ringtones using RingtoneManager.getCursor()
(docs) and add them to the list, and include your custom ringtones also.
Then, in your preferences.xml file, instead of RingtonePreference, you would use
<com.yourapp.CustomPreference android:key="your_key"
...
In your case, since it's okay if other apps have access to the ringtones as well, I'd recommend using the first method, since in general I feel it's better not to duplicate functionality already provided by the system, since it's best to utilize what the user is already familiar with.
Finally I made my own ExtraRingtonePreference based on this answer: In preferences, select my sound just like with RingtonePreference
I'll include it here for future reference:
src/main/java/com/fletech/android/preference/ExtraRingtonePreference.java:
package com.fletech.android.preference;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import com.fletech.android.redalert.R;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
public class ExtraRingtonePreference extends DialogPreference {
private Context mContext;
private String mValue;
private Ringtone ringtone;
private int mRingtoneType;
private boolean mShowSilent;
private boolean mShowDefault;
private CharSequence[] mExtraRingtones;
private CharSequence[] mExtraRingtoneTitles;
public ExtraRingtonePreference(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ExtraRingtonePreference, 0, 0);
mRingtoneType = a.getInt(R.styleable.ExtraRingtonePreference_ringtoneType, RingtoneManager.TYPE_RINGTONE);
mShowDefault = a.getBoolean(R.styleable.ExtraRingtonePreference_showDefault, true);
mShowSilent = a.getBoolean(R.styleable.ExtraRingtonePreference_showSilent, true);
mExtraRingtones = a.getTextArray(R.styleable.ExtraRingtonePreference_extraRingtones);
mExtraRingtoneTitles = a.getTextArray(R.styleable.ExtraRingtonePreference_extraRingtoneTitles);
a.recycle();
}
public ExtraRingtonePreference(Context context) {
this(context, null);
}
public String getValue() {
return mValue;
}
private Map<String, Uri> getSounds(int type) {
RingtoneManager ringtoneManager = new RingtoneManager(mContext);
ringtoneManager.setType(type);
Cursor cursor = ringtoneManager.getCursor();
Map<String, Uri> list = new TreeMap<String, Uri>();
while (cursor.moveToNext()) {
String notificationTitle = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
Uri notificationUri = ringtoneManager.getRingtoneUri(cursor.getPosition());
list.put(notificationTitle, notificationUri);
}
return list;
}
private Uri uriFromRaw(String name) {
int resId = mContext.getResources().getIdentifier(name, "raw", mContext.getPackageName());
return Uri.parse("android.resource://" + mContext.getPackageName() + "/" + resId);
}
public String getExtraRingtoneTitle(CharSequence name) {
if (mExtraRingtones != null && mExtraRingtoneTitles != null) {
int index = Arrays.asList(mExtraRingtones).indexOf(name);
return mExtraRingtoneTitles[index].toString();
}
return null;
}
@Override
public CharSequence getSummary() {
String ringtoneTitle = null;
if (mValue != null) {
if (mValue.length() == 0)
ringtoneTitle = mContext.getString(R.string.silent);
Uri mValueUri = Uri.parse(mValue);
if (ringtoneTitle == null && mExtraRingtones != null && mExtraRingtoneTitles != null) {
for (int i = 0; i < mExtraRingtones.length; i++) {
Uri uriExtra = uriFromRaw(mExtraRingtones[i].toString());
if (uriExtra.equals(mValueUri)) {
ringtoneTitle = mExtraRingtoneTitles[i].toString();
break;
}
}
}
if (ringtoneTitle == null) {
Ringtone ringtone = RingtoneManager.getRingtone(mContext, mValueUri);
if (ringtone != null) {
String title = ringtone.getTitle(mContext);
if (title != null && title.length() > 0) {
ringtoneTitle = title;
}
}
}
}
CharSequence summary = super.getSummary();
if (ringtoneTitle != null) {
// if (summary != null)
// return String.format(summary.toString(), ringtoneTitle);
// else
return ringtoneTitle;
} else return summary;
}
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
final Map<String, Uri> sounds = new LinkedHashMap<String, Uri>();
if (mExtraRingtones != null) {
for (CharSequence extraRingtone : mExtraRingtones) {
Uri uri = uriFromRaw(extraRingtone.toString());
String title = getExtraRingtoneTitle(extraRingtone);
sounds.put(title, uri);
}
}
if (mShowSilent)
sounds.put(mContext.getString(R.string.silent), Uri.parse(""));
if (mShowDefault) {
Uri uriDefault = RingtoneManager.getDefaultUri(mRingtoneType);
if (uriDefault != null) {
Ringtone ringtoneDefault = RingtoneManager.getRingtone(mContext, uriDefault);
if (ringtoneDefault != null) {
sounds.put(ringtoneDefault.getTitle(mContext), uriDefault);
}
}
}
sounds.putAll(getSounds(mRingtoneType));
final String[] titleArray = sounds.keySet().toArray(new String[0]);
final Uri[] uriArray = sounds.values().toArray(new Uri[0]);
int index = mValue != null ? Arrays.asList(uriArray).indexOf(Uri.parse(mValue)) : -1;
builder.setSingleChoiceItems(titleArray, index, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (ringtone != null)
ringtone.stop();
Uri uri = uriArray[which];
if (uri != null) {
if (uri.toString().length() > 0) {
ringtone = RingtoneManager.getRingtone(mContext, uri);
if (ringtone != null) {
ringtone.play();
}
}
mValue = uri.toString();
} else mValue = null;
}
});
builder.setPositiveButton(R.string.dialog_save, this);
builder.setNegativeButton(R.string.dialog_cancel, this);
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (ringtone != null)
ringtone.stop();
if (positiveResult && callChangeListener(mValue)) {
persistString(mValue);
notifyChanged();
}
}
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getString(index);
}
@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
if (restoreValue) {
mValue = getPersistedString("");
} else {
if (mExtraRingtones != null && defaultValue != null && defaultValue.toString().length() > 0) {
int index = Arrays.asList(mExtraRingtones).indexOf((CharSequence) defaultValue);
if (index >= 0) {
mValue = uriFromRaw(defaultValue.toString()).toString();
} else {
mValue = (String)defaultValue;
}
} else {
mValue = (String)defaultValue;
}
persistString(mValue);
}
}
}
src/main/res/values.attrs.xml:
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<declare-styleable name="ExtraRingtonePreference">
<attr name="ringtoneType"><!-- Should correspond to RingtoneManager -->
<!-- TYPE_RINGTONE: Ringtones. -->
<flag name="ringtone" value="1" />
<!-- TYPE_NOTIFICATION: Notification sounds. -->
<flag name="notification" value="2" />
<!-- TYPE_ALARM: Alarm sounds. -->
<flag name="alarm" value="4" />
<!-- TYPE_ALL: All available ringtone sounds. -->
<flag name="all" value="7" />
</attr>
<attr name="showSilent" format="boolean"/>
<attr name="showDefault" format="boolean"/>
<attr name="extraRingtones" format="reference"/>
<attr name="extraRingtoneTitles" format="reference"/>
</declare-styleable>
</resources>
src/res/values/ringtone_preference_strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="ringtone_title">Ringtone</string>
<string name="silent">Silent</string>
<string name="dialog_save">Save</string>
<string name="dialog_cancel">Cancel</string>
<string-array name="extra_ringtones">
<item>beep</item>
<item>beep_beep</item>
<item>default</item>
<item>test</item>
</string-array>
<string-array name="extra_ringtone_titles">
<item>Beep</item>
<item>Beep-Beep</item>
<item>Default</item>
<item>Test</item>
</string-array>
</resources>
and the usage in src/main/res/xml/pref_alarm.xml:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:auto="http://schemas.android.com/apk/res-auto">
<com.fletech.android.preference.ExtraRingtonePreference
android:key="notifications_alarm_ringtone"
android:title="@string/ringtone_title"
android:defaultValue="default"
auto:ringtoneType="alarm"
auto:showSilent="true"
auto:showDefault="true"
auto:extraRingtones="@array/extra_ringtones"
auto:extraRingtoneTitles="@array/extra_ringtone_titles"/>
</PreferenceScreen>