How can I add my app's custom ringtones in res/raw folder to a ringtonepreference
Asked Answered
S

2

5

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.)

Stag answered 30/7, 2014 at 20:49 Comment(8)
I think you'll need to either create a custom Preference, or copy them to the device's shared storage (then they would be available to other apps too).Fading
I don't want the ringtones in my app's res/raw folder to be available for other apps as ringtone (though it's not a problem if they are). I would like to show a list with the available ringtones + my custom ringtones. There should be a standard way to do this, because when I go to Android settings > Sound > Ringtone, then I see 5 apps as possible Intents. Some of them only show their custom ringtones, but other do show also other apps' ringtones.Stag
So you can create a custom preference. There's a guide in the documentation site.Fading
And if I will have my preference, then how can I pop up the intent picker for the "Ringtone Picker" action? I mean, I guess I'll need to add my custom preferencescreen to the Manifest, and filter it for this "ringtone picker" action, but what is it?Stag
You don't need an intent filter, just use a custom preference instead of the RingtonePreference. See my answer.Fading
I updated my answer to add another (imo better) option, to copy the files to the device storage.Fading
See my class "ExtraRingtonePreference" at https://mcmap.net/q/972514/-in-preferences-select-my-sound-just-like-with-ringtonepreferenceAirman
@almisoft, very nice! If you write this as an answer, I'll accept itStag
S
4

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>
Stag answered 17/1, 2016 at 10:12 Comment(0)
F
5

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.

Fading answered 14/8, 2014 at 0:26 Comment(0)
S
4

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>
Stag answered 17/1, 2016 at 10:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.