In preferences, select my sound just like with RingtonePreference
Asked Answered
N

4

11

I have sounds in my /raw folder and I would like my user to be able to choose one sound in preferences exactly like RingtonePreference does but only with my sounds.

Northington answered 3/8, 2012 at 8:31 Comment(0)
A
18

Here my RingtonePreference replacement. All system ringtones and your custom ringtones (defined in xml, stored in res/raw) are listed:

ExtraRingtonePreference.java

package de.almisoft.test;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;

import de.almisoft.test.R;

import android.app.AlertDialog.Builder;
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;

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);
    }

    private 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);

            if (ringtoneTitle == null && mExtraRingtones != null && mExtraRingtoneTitles != null) {

                for (int i = 0; i < mExtraRingtones.length; i++) {

                    Uri uriExtra = uriFromRaw(mExtraRingtones[i].toString());

                    if (uriExtra.equals(Uri.parse(mValue))) {
                        ringtoneTitle = mExtraRingtoneTitles[i].toString();
                        break;
                    }
                }
            }

            if (ringtoneTitle == null) {
                Ringtone ringtone = RingtoneManager.getRingtone(mContext, Uri.parse(mValue));
                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(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 (mShowDefault) {
            Uri uriDefault = RingtoneManager.getDefaultUri(mRingtoneType);
            if (uriDefault != null) {
                Ringtone ringtoneDefault = RingtoneManager.getRingtone(mContext, uriDefault);
                if (ringtoneDefault != null) {
                    sounds.put(ringtoneDefault.getTitle(mContext), uriDefault);
                }
            }   
        }

        if (mShowSilent) 
            sounds.put(mContext.getString(R.string.silent), Uri.parse(""));


        sounds.putAll(getSounds(RingtoneManager.TYPE_NOTIFICATION));


        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();

                String title = titleArray[which];
                Uri uri = uriArray[which];

                if (uri != null) {
                    if (uri.toString().length() > 0) {
                        ringtone = RingtoneManager.getRingtone(mContext, uri);
                        ringtone.play();
                    }
                    mValue = uri.toString();
                } else mValue = null;

            }
        });

        builder.setPositiveButton(R.string.ok, this);
        builder.setNegativeButton(R.string.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);
        }


    }


}

res/values/attrs.xml

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <declare-styleable name="ExtraRingtonePreference">
        <attr name="ringtoneType">
            <!-- Ringtones. -->
            <flag name="ringtone" value="1" />
            <!-- Notification sounds. -->
            <flag name="notification" value="2" />
            <!-- Alarm sounds. -->
            <flag name="alarm" value="4" />
            <!-- 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>

res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="silent">Silent</string>
    <string name="ok">OK</string>
    <string name="cancel">Cancel</string>
    <string name="ringtoneTitle">Ringtone</string>
    <string name="ringtoneSummary">Ringtone: %s</string>

    <string-array name="extraRingtones">
        <item>deichkind_sone_musik</item>
        <item>madonna_like_a_virgin</item>
    </string-array>

    <string-array name="extraRingtoneTitles">
        <item>Sone Musik</item>
        <item>Like A Virgin</item>
    </string-array>
</resources>

res/raw

res
 ↳ raw
    ↳ deichkind_sone_musik.mp3
    ↳ madonna_like_a_virgin.mp3

res/xml/preferences.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">

    <de.almisoft.test.ExtraRingtonePreference
        android:key="ringtone"
        android:title="@string/ringtoneTitle"
        android:summary="@string/ringtoneSummary"
        android:defaultValue="deichkind_sone_musik"
        auto:ringtoneType="notification"
        auto:showSilent="true"
        auto:showDefault="true"
        auto:extraRingtones="@array/extraRingtones"
        auto:extraRingtoneTitles="@array/extraRingtoneTitles"/>

    <!-- set android:defaultValue 
         to "deichkind_sone_musik" for your custom mp3
         to "" for silent
         to "content://settings/system/notification_sound" for system default ringtone -->

</PreferenceScreen>    
Atiptoe answered 23/6, 2015 at 13:38 Comment(6)
Works almost out of the box, just a little fix: remove the context parameter from uriFromRaw() and use mContext Also, as a feature request, it would be nice to display the selected ringtone's name in the 2nd line (description) of the preference, so the user can see the selected ringtone without the need to enter the popupPiecrust
Done! removed context param from uriFromRaw(), add getSummary() :-)Atiptoe
thanks! It was fast! It works after I change the ringtone manually to anything I chose. After it the summary is displayed even when I only get to the preference screen. But BEFORE I set the ringtone for the 1st time the summary is empty. I guess, it's OK, but is it possible to add a default value in the xml (or somehow)? For example auto:defaultValue="@+id/madonna_like_a_virgin", auto:defaultValue="" (for the system default ringtone) or auto:defaultValue="null" (for silent)Piecrust
I've fixed onSetInitialValue(). Now, you can set android:defaultValue to e.g. "deichkind_sone_musik" for your custom mp3, or to "" for silent or to "content://settings/system/notification_sound" for system default ringtone. If there is no android:defaultValue nothing is selected in list dialog and summary is blank.Atiptoe
works out of the box! this should be marked as the answer. great stuff @AtiptoeInalterable
That looks awesome and I had high hopes.Unfortunately it doesn't work with androidx, and I don't yet have the knowledge to adapt it. Sad.Bruise
N
3

So finally I looked into the source code of ListPreference and made the same with some modifcations. As I can't use com.android.internal.R.styleable.ListPreference I had to create my own styleable in attrs.xml :

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="ListPreference">
        <attr name="entries" format="string"></attr>
        <attr name="entryValues" format="string"></attr>
    </declare-styleable>
    <declare-styleable name="Preference">
        <attr name="summary" format="string"></attr>
    </declare-styleable>
</resources>

and then import it in my preferences.xml file like this:

 xmlns:foo="http://schemas.android.com/apk/res/com.abe.abemoto"

and uses it :

    <com.abe.abemoto.preference.CustomSoundListPreference
        android:defaultValue="@string/pref_alert_ring_value_1"
        android:key="@string/pref_alert_sound_choice_for_notif_key"
        android:title="Sonnerie de notification"
        foo:entries="@array/pref_alert_ring_entries"
        foo:entryValues="@array/pref_alert_ring_values"
        foo:summary="Choisissez la sonnerie pour les notifications" />

In my class CustomSoundListPreference I modified the method onPrepareDialogBuilder to play my sound on item clicked.

    @Override
protected void onPrepareDialogBuilder(Builder builder) {
    super.onPrepareDialogBuilder(builder);

    mMediaPlayer = new MediaPlayer();

    if (mEntries == null || mEntryValues == null) {
        throw new IllegalStateException(
                "ListPreference requires an entries array and an entryValues array.");
    }

    mClickedDialogEntryIndex = getValueIndex();
    builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    mClickedDialogEntryIndex = which;

                    String value = mEntryValues[which].toString();

                    Resources res = getContext().getResources();
                    int resId = res.getIdentifier(value, "raw",
                            getContext().getPackageName());

                    Uri uri = Uri.parse(String.format(getContext()
                            .getString(R.string.resource_sound),
                            getContext().getPackageName(), resId));

                    Log.d(TAG, "uri sound = " + uri);
                    try {
                        mMediaPlayer.reset();
                        mMediaPlayer.setDataSource(getContext(), uri);
                        mMediaPlayer.prepare();
                        mMediaPlayer.start();

                    } catch (IllegalStateException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
            });

    builder.setPositiveButton("Ok", this);
    builder.setNegativeButton("Annuler", this);
}
Northington answered 5/8, 2012 at 15:27 Comment(0)
C
2

Here is the full code for a custom ListPreference just like a ringtone preference:

import java.io.IOException;

import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.ListPreference;
import android.provider.MediaStore;
import android.util.AttributeSet;
import android.util.Log;

public class CustomListPreference extends ListPreference{

private MediaPlayer mMediaPlayer;
private Context mContext;
CharSequence[] mEntries;
CharSequence[] mEntryValues;
private int mClickedDialogEntryIndex;
private String mValue;

public CustomListPreference(Context context) {
    super(context);
    mContext = context;
}

/**
 * Sets the value of the key. This should be one of the entries in
 * {@link #getEntryValues()}.
 * 
 * @param value The value to set for the key.
 */
public void setValue(String value) {
    mValue = value;

    persistString(value);
}

/**
 * Sets the value to the given index from the entry values.
 * 
 * @param index The index of the value to set.
 */
public void setValueIndex(int index) {
    if (mEntryValues != null) {
        setValue(mEntryValues[index].toString());
    }
}

/**
 * Returns the value of the key. This should be one of the entries in
 * {@link #getEntryValues()}.
 * 
 * @return The value of the key.
 */
public String getValue() {
    return mValue; 
}

/**
 * Returns the entry corresponding to the current value.
 * 
 * @return The entry corresponding to the current value, or null.
 */
public CharSequence getEntry() {
    int index = getValueIndex();
    return index >= 0 && mEntries != null ? mEntries[index] : null;
}

 public int findIndexOfValue(String value) {
    if (value != null && mEntryValues != null) {
        for (int i = mEntryValues.length - 1; i >= 0; i--) {
            if (mEntryValues[i].equals(value)) {
                return i;
            }
        }
    }
    return -1;
}

private int  getValueIndex() {

    return findIndexOfValue(mValue);
}

@Override
protected void onPrepareDialogBuilder(Builder builder) {
    super.onPrepareDialogBuilder(builder);

    mMediaPlayer = new MediaPlayer();
    mEntries = getEntries();
    mEntryValues = getEntryValues();

    if (mEntries == null || mEntryValues == null) {
        throw new IllegalStateException(
                "ListPreference requires an entries array and an entryValues array.");
    }

    mClickedDialogEntryIndex = getValueIndex();
    builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
            new DialogInterface.OnClickListener() {

                public void onClick(DialogInterface dialog, int which) {
                    mClickedDialogEntryIndex = which;

                    String value = mEntryValues[which].toString();
                    String path = findPathFromName(value);

                    try {
                        playSong(path);
                    } catch (IllegalStateException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });

    builder.setPositiveButton("Ok", this);
    builder.setNegativeButton("Cancel", this);
}

private void playSong(String path) throws IllegalArgumentException,
    IllegalStateException, IOException {

    Log.d("ringtone", "playSong :: " + path);

    mMediaPlayer.reset();
    mMediaPlayer.setDataSource(path);
    mMediaPlayer.setAudioStreamType(AudioManager.STREAM_RING);
//  mMediaPlayer.setLooping(true);
    mMediaPlayer.prepare();
    mMediaPlayer.start();
}


public String findPathFromName(String name) {
    Cursor mCursor = getContext().getContentResolver().query(
            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, 
            MediaStore.Audio.Media.TITLE  + "='" +  name + "'", null, null );

    String path = "";
    if(mCursor.moveToFirst()){
        path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Audio.Media.DATA));
    }

    mCursor.close();
    mCursor = null;

    return path;
}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    if (state == null || !state.getClass().equals(SavedState.class)) {
        // Didn't save state for us in onSaveInstanceState
        super.onRestoreInstanceState(state);
        return;
    }

    SavedState myState = (SavedState) state;
    super.onRestoreInstanceState(myState.getSuperState());
    setValue(myState.value);
}

private static class SavedState extends BaseSavedState {
    String value;

    public SavedState(Parcel source) {
        super(source);
        value = source.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        dest.writeString(value);
    }

    public SavedState(Parcelable superState) {
        super(superState);
    }

    public static final Parcelable.Creator<SavedState> CREATOR =
            new Parcelable.Creator<SavedState>() {
        public SavedState createFromParcel(Parcel in) {
            return new SavedState(in);
        }

        public SavedState[] newArray(int size) {
            return new SavedState[size];
        }
    };
}

 @Override
protected void onDialogClosed(boolean positiveResult) {
    super.onDialogClosed(positiveResult);

    if (positiveResult && mClickedDialogEntryIndex >= 0 && mEntryValues != null) {
        String value = mEntryValues[mClickedDialogEntryIndex].toString();
        if (callChangeListener(value)) {
            setValue(value);
        }
    }

    mMediaPlayer.stop();
    mMediaPlayer.release();
}

@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
    return a.getString(index);
}

@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
    setValue(restoreValue ? getPersistedString(mValue) : (String) defaultValue);
}

@Override
protected Parcelable onSaveInstanceState() {
    final Parcelable superState = super.onSaveInstanceState();
    if (isPersistent()) {
        // No need to save instance state since it's persistent
        return superState;
    }

    final SavedState myState = new SavedState(superState);
    myState.value = getValue();
    return myState;
}
}

Hope this will be helpful to someone.

Cerebroside answered 27/2, 2013 at 12:47 Comment(0)
I
0

when making a ringtone preference customization i prefer that the ringtone only play for short while, sort of like a sample sounds. user does not need to hear the entire sound play if they are just choosing a sound from a list. Here is how i achieve this:

first create a service that will play the ringtone (well use ringtone manager to play the sound instead of media player as its handling the cancelling for us):

public class PlayRingtoneService extends Service { static Ringtone r; private Handler handler;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    //activating alarm sound
    if (r != null)
        r.stop();

    String filePath = intent.getStringExtra("uri");
    r = RingtoneManager.getRingtone(this, Uri.parse(filePath));
    r.play();

    handler.removeCallbacksAndMessages(null);

    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            if(r!=null)
                r.stop();
        }
    },6000L); //stop sound in 6 seconds

    return super.onStartCommand(intent, flags, startId);
}

void setThreadPriority(int priority) {

    try {
        Process.setThreadPriority(priority);
    } catch (Exception e) {
        Timber.e(e);
    }

}

@Override
public void onCreate() {
    super.onCreate();
     handler =new Handler();
    setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);

}

@Override
public void onDestroy() {
    if (r != null)
        r.stop();
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

}

also update the manifest with the service:

<service android:name=".services.PlayRingtoneService" />

then using the solutions above but modified you can create a list preference that behaves just like a ringtone preference:

public class CustomRingtoneListPreference extends ListPreference {

CharSequence[] mEntries;
CharSequence[] mEntryValues;
private int mClickedDialogEntryIndex;
private String mValue;

public CustomRingtoneListPreference(Context context) {
    super(context);
}


public CustomRingtoneListPreference(Context context, AttributeSet attrs) {
    super(context, attrs);
}

/**
 * Returns the value of the key. This should be one of the entries in
 * {@link #getEntryValues()}.
 *
 * @return The value of the key.
 */
public String getValue() {
    return mValue;
}

/**
 * Sets the value of the key. This should be one of the entries in
 * {@link #getEntryValues()}.
 *
 * @param value The value to set for the key.
 */
public void setValue(String value) {
    mValue = value;

    persistString(value);
}

/**
 * Returns the entry corresponding to the current value.
 *
 * @return The entry corresponding to the current value, or null.
 */
public CharSequence getEntry() {
    int index = getValueIndex();
    return index >= 0 && mEntries != null ? mEntries[index] : null;
}

public int findIndexOfValue(String value) {
    if (value != null && mEntryValues != null) {
        for (int i = mEntryValues.length - 1; i >= 0; i--) {
            if (mEntryValues[i].equals(value)) {
                return i;
            }
        }
    }
    return -1;
}

private int getValueIndex() {

    return findIndexOfValue(mValue);
}

/**
 * Sets the value to the given index from the entry values.
 *
 * @param index The index of the value to set.
 */
public void setValueIndex(int index) {
    if (mEntryValues != null) {
        setValue(mEntryValues[index].toString());
    }
}

@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
    super.onPrepareDialogBuilder(builder);
    mEntries = getEntries();
    mEntryValues = getEntryValues();

    if (mEntries == null || mEntryValues == null) {
        throw new IllegalStateException(
                "ListPreference requires an entries array and an entryValues array.");
    }

    mClickedDialogEntryIndex = getValueIndex();
    builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
            new DialogInterface.OnClickListener() {

                public void onClick(DialogInterface dialog, int which) {
                    mClickedDialogEntryIndex = which;

                    String value = mEntryValues[which].toString();
                        playSong(value);
                }
            });

    builder.setPositiveButton("Ok", this);
    builder.setNegativeButton("Cancel", this);
}

private void playSong(String path) {
    Intent i = new Intent(getContext(), PlayRingtoneService.class);
    i.putExtra("uri", path);
    getContext().startService(i);

}

@Override
protected void onRestoreInstanceState(Parcelable state) {
    if (state == null || !state.getClass().equals(SavedState.class)) {
        // Didn't save state for us in onSaveInstanceState
        super.onRestoreInstanceState(state);
        return;
    }

    SavedState myState = (SavedState) state;
    super.onRestoreInstanceState(myState.getSuperState());
    setValue(myState.value);
}

@Override
protected void onDialogClosed(boolean positiveResult) {
    super.onDialogClosed(positiveResult);

    if (positiveResult && mClickedDialogEntryIndex >= 0 && mEntryValues != null) {
        String value = mEntryValues[mClickedDialogEntryIndex].toString();
        if (callChangeListener(value)) {
            setValue(value);
        }
    }

    Intent i = new Intent(getContext(), PlayRingtoneService.class);
    getContext().stopService(i);
}

@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
    return a.getString(index);
}

@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {

    setValue(restoreValue ? getPersistedString(mValue) : (String) defaultValue);
}

@Override
protected Parcelable onSaveInstanceState() {
    final Parcelable superState = super.onSaveInstanceState();
    if (isPersistent()) {
        // No need to save instance state since it's persistent
        return superState;
    }

    final SavedState myState = new SavedState(superState);
    myState.value = getValue();
    return myState;
}

private static class SavedState extends BaseSavedState {
    public static final Parcelable.Creator<SavedState> CREATOR =
            new Parcelable.Creator<SavedState>() {
                public SavedState createFromParcel(Parcel in) {
                    return new SavedState(in);
                }

                public SavedState[] newArray(int size) {
                    return new SavedState[size];
                }
            };
    String value;

    public SavedState(Parcel source) {
        super(source);
        value = source.readString();
    }

    public SavedState(Parcelable superState) {
        super(superState);
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        super.writeToParcel(dest, flags);
        dest.writeString(value);
    }
}

}

now in your xml use it like this:

<mypackage.blah.blah.CustomRingtoneListPreference
            android:key="myRingtone"
            android:title="my title"
            android:summary="ringtone chosen %s"
            android:defaultValue="0"
            android:dependency="whatever you have"

            />

now to actually load up the values into the list we create a utils method that can get all internal and external media and put it into our model class called songs defined like this:

public class Song {

private long id;
private Uri filePath;

private boolean externalPath;


/**
 * Creates a new Song, with specified `songID` and `filePath`.
 *
 * @note It's a unique Android identifier for a media file
 * anywhere on the system.
 */
public Song(long id, String title, String artist, Uri fileUri, boolean externalPath) {
    this.id = id;
    this.title = title;
    this.artist = artist;
    this.filePath = fileUri;
    this.externalPath = externalPath;
}

/**
 * Identifier for the song on the Android system.
 * (so we can locate the file anywhere)
 */
public long getId() {
    return id;
}


public Uri getFilePath() {
    return filePath;
}

public Song setFilePath(Uri filePath) {
    this.filePath = filePath;
    return this;
}

private String title = "";
private String artist = "";


public String getTitle() {
    return title;
}

public void setTitle(String title) {
    this.title = title;
}


public String getArtist() {
    return artist;
}

public void setArtist(String artist) {
    this.artist = artist;
}

public boolean isExternalPath() {
    return externalPath;
}

public Song setIsExternalPath(boolean externalPath) {
    this.externalPath = externalPath;
    return this;
}

}

Now in your Util class or just a static method if you want you do make this class which will query the media store for all audio files:

public static List getAllExternalAudioSongs(Context c) { List songList = new ArrayList<>(); ContentResolver contentResolver = c.getContentResolver();

    List<Uri> contentUriLists = new ArrayList<>();
    contentUriLists.add(MediaStore.Audio.Media.INTERNAL_CONTENT_URI);
    contentUriLists.add(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
    String selection= MediaStore.Audio.Media.DURATION + ">= 3000";

    boolean externalPath = false;

    for (Uri uri : contentUriLists) {
        Cursor cursor = contentResolver.query(uri, null, selection, null, android.provider.MediaStore.Audio.Media.TITLE+ " ASC");
        if (cursor == null) {
            // query failed, handle error.
        } else if (!cursor.moveToFirst()) {
            // no media on the device
        } else {
            int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
            int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
            int artistColumn = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);

            do {
                long id = cursor.getLong(idColumn);
                String title = cursor.getString(titleColumn);
                String artist = cursor.getString(artistColumn);
                Uri contentUri = ContentUris.withAppendedId(
                        uri, id);
                Song song = new Song(id, title, artist, contentUri, externalPath);
                songList.add(song);
            } while (cursor.moveToNext());
            externalPath=true;
        }
    }
    return songList;
}

note: the externalPath is just if you want to differentiate internal from external audio files.

Finally, in onCreate of preferenceActivity (or fragment) you can do this:

private void setRingtoneList() {

    ListPreference listPreferenceCategory = (ListPreference) findPreference("myRingtone");
    if (listPreferenceCategory != null) {
        List<Song> songList = Utils.getAllExternalAudioSongs(getApplicationContext());
        CharSequence entries[] = new String[songList.size()];
        CharSequence entryValues[] = new String[songList.size()];
        int i = 0;
        for (Song song : songList) {
            entries[i] = song.getTitle();
            entryValues[i] = song.getFilePath().toString();
            i++;
        }
        listPreferenceCategory.setEntries(entries);
        listPreferenceCategory.setEntryValues(entryValues);
    }
}

note: you'll need runtime permissions for external storage. and also to update the summary youll have to do that in the preference activity i believe. anyway this gives a good idea how to play sample audio instead of entire audio file.

Inchoative answered 9/12, 2017 at 7:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.