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.
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:
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 CREATOR =
new Parcelable.Creator() {
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:
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 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 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.