Android: Saving a sound file to SD from resource and setting as ringtone
Quite a few people have been asking how to save a file to the SD card in order to register it as a ringtone. The following example creates a function that will save a resource to the SD card (ie: from R.raw.soundfile to /sdcard/media/audio/ringtones/soundfile.wav) and register it as a ringtone.
I have split this example into two parts, the first part goes through the code a section at a time with a brief explanation of what it does, the second half is just the code that you can copy and paste and then edit to your hearts content.
Parts
We first setup our function to return a boolean depicting if we have failed or if we are successful. We accept in an integer which corresponds to the raw sound file.
public boolean saveas(int ressound){
}
So this function would be called in the following fashion:
saveas(R.raw.soundfile);
or utilising its boolean return:
if (saveas(R.raw.soundfile)){
// Code if successful
}
else
{
// Code if unsuccessful
}
The following chunk of code creates an inputstream from the raw sound resource and loads it into a buffer. We add in the mandatory try/catch clause around these operations and return false if an exception is raised (to indicate failure to the rest of our program and to prevent trying to continue act upon this sound).
byte[] buffer=null;
InputStream fIn = getBaseContext().getResources().openRawResource(ressound);
int size=0;
try {
size = fIn.available();
buffer = new byte[size];
fIn.read(buffer);
fIn.close();
} catch (IOException e) {
// TODO Auto-generated catch block
return false;
}
The following saves the buffer to a file on the SD card. It first ensures the folder exists and if not it is created. Then as before the writing operations are surrounded with try/catches
String path="/sdcard/media/audio/ringtones/";
String filename="examplefile"+".wav";
boolean exists = (new File(path)).exists();
if (!exists){new File(path).mkdirs();}
FileOutputStream save;
try {
save = new FileOutputStream(path+filename);
save.write(buffer);
save.flush();
save.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
return false;
} catch (IOException e) {
// TODO Auto-generated catch block
return false;
}
The following code sends an intent to tell the Media Scanner that we have added a new file, and sets up its properties in the media database:
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://"+path+filename)));
File k = new File(path, filename);
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, k.getAbsolutePath());
values.put(MediaStore.MediaColumns.TITLE, "exampletitle");
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/wav");
values.put(MediaStore.Audio.Media.ARTIST, "cssounds ");
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
values.put(MediaStore.Audio.Media.IS_ALARM, true);
values.put(MediaStore.Audio.Media.IS_MUSIC, false);
//Insert it into the database
this.getContentResolver().insert(MediaStore.Audio.Media.getContentUriForPath(k.getAbsolutePath()), values);
Final code
Putting all this code together gives us our final functions:
public boolean saveas(int ressound){
byte[] buffer=null;
InputStream fIn = getBaseContext().getResources().openRawResource(ressound);
int size=0;
try {
size = fIn.available();
buffer = new byte[size];
fIn.read(buffer);
fIn.close();
} catch (IOException e) {
// TODO Auto-generated catch block
return false;
}
String path="/sdcard/media/audio/ringtones/";
String filename="examplefile"+".wav";
boolean exists = (new File(path)).exists();
if (!exists){new File(path).mkdirs();}
FileOutputStream save;
try {
save = new FileOutputStream(path+filename);
save.write(buffer);
save.flush();
save.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
return false;
} catch (IOException e) {
// TODO Auto-generated catch block
return false;
}
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://"+path+filename)));
File k = new File(path, filename);
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, k.getAbsolutePath());
values.put(MediaStore.MediaColumns.TITLE, "exampletitle");
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/wav");
values.put(MediaStore.Audio.Media.ARTIST, "cssounds ");
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
values.put(MediaStore.Audio.Media.IS_ALARM, true);
values.put(MediaStore.Audio.Media.IS_MUSIC, false);
//Insert it into the database
this.getContentResolver().insert(MediaStore.Audio.Media.getContentUriForPath(k.getAbsolutePath()), values);
return true;
}
Comments, suggestions etc. are always welcome. Hope this has been helpful.




This is great I have this part working. Can you explain more about the context menu and how it knows what file per button?
Ok sure that’s a good idea, I’ll write up a example tomorrow on using context menus.
Thanks!
I expanded your saveas(); to include a string used for the soundname that can be used later in the code
public boolean saveas(int ressound, String soundname){
Is it a good idea to do that?
Yes that’s a good idea. In my application I created two arrays one for the sound files and one for the names of the sound files. Then I simply passed my function an integer which corresponded to the file in one array and the matching text in the other array. This meant that I only needed to change the array’s if I were to change all the sound files.
Using this code, there seem to be two issues: (1) that the file appears as a ringtone, but with the filename and not the title as its menu title. (2) that i can’t seem to categorize it as a notification tone and not a ring tone. Any clues?
Looking into it a bit further, it seems that what really happens is that the MediaStore entry gets changed and erases the Title/Artist that is put in there and replaces it with /. Is anyone else experiencing this?
That seems very weird I’ll do some testing soon to see if I can reproduce the problem,
just curious.. did you put all of this into a OnLongClick? if not how was the user able to select to save as ringtone?
Hi, im having problems getting this to work, it wont seem to copy the raw sound file to the sd card, I have tried adding the storage permissions but this hasnt helped. my raw files are either an .ogg or an .mp3 but this should still work? Thanks.
I’d also like to know how I can utilise this code with an on long click button press, how would I implement it.
@Chris, yes this was implemented onlong click where the can choose between a ringtone and a notification
@Jake, It’s hard to help without knowing what the error is, can you check the logs (adb logcat) when the error occurs?
@Wildheart Baby, you can override the default onCreateContextMenu in your activity and create a context menu and override the onContextItemSelected which then calls a function what to do based on which view the press originated (differentiate by the view’s ID). I will add a short write up of this soon.
Created a quick tutorial on how to create context menus:
http://www.stealthcopter.com/blog/2010/04/android-context-menu-example-on-long-press-gridview/
It would be really nice if you could link the context menu guide and the save files to sd card guide.
I am having trouble with my soundboard saving files…
hey matt..
so we have a junk load of soundboards on the market now… but one main issue is after you save the sound file for notification or ringtone, if the phone loses power or something, then you have to re-save the sounds… our save code resembles yours very muhc, I was woundering if you’ve had this issue at all with yours?
Great stuff here, Matt. I really appreciate the info and the presentation.
I have a question, though, and it might be super duper noobish.. which would equate to my level of Java skills pretty accurately. *sigh*
Anyway here goes: I’ve got the onlongclick button working correctly, and I’ve implemented this code as well, I think correctly. When I run the app in a virtual machine, however, I get a force close when I try to click on my “save as ringtone” button in my onlongclick menu. is this merely because I am using a virtual machine without a space to store the sound file or is there something I’ve neglected in the code itself?
I’m not sure if that made perfect sense, so I apologize if it was unclear or missing details.
Again, this is a huge help for me as a Java beginner!
Well to be completely sure what is causing your FC you should check logcat, but I agree with you if it tries to save to a place that doesnt exist, it will def force close. Please feel free to contact me via email in future as I will reply quicker
android at stealthcopter.com
Thank you so much!
I have been struggling with how to play the music from my apk for 2 weeks! I’m using Processing with MediaPlayer and I was stumped; but a combination of your code and createInput() finally worked!
great help thanks!!
Quick question – it works perfect for ringtones, but for notifications I get exception when I try to invoke the Ringtone picker and click on this newly added entry (or any entry) – here is the exception, appriciate much any idea…
07-22 15:51:03.241: ERROR/AndroidRuntime(316): Uncaught handler: thread main exiting due to uncaught exception
07-22 15:51:03.271: ERROR/AndroidRuntime(316): android.database.CursorIndexOutOfBoundsException: Index 0 requested, with a size of 0
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.database.AbstractCursor.checkPosition(AbstractCursor.java:580)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.database.AbstractWindowedCursor.checkPosition(AbstractWindowedCursor.java:172)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:41)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.database.CursorWrapper.getString(CursorWrapper.java:135)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at com.android.internal.database.SortCursor.getString(SortCursor.java:205)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.media.RingtoneManager.getUriFromCursor(RingtoneManager.java:403)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.media.RingtoneManager.getRingtoneUri(RingtoneManager.java:399)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.media.RingtoneManager.getRingtone(RingtoneManager.java:382)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at com.android.internal.app.RingtonePickerActivity.run(RingtonePickerActivity.java:307)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.os.Handler.handleCallback(Handler.java:587)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.os.Handler.dispatchMessage(Handler.java:92)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.os.Looper.loop(Looper.java:123)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at android.app.ActivityThread.main(ActivityThread.java:4203)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at java.lang.reflect.Method.invokeNative(Native Method)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at java.lang.reflect.Method.invoke(Method.java:521)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:549)
07-22 15:51:03.271: ERROR/AndroidRuntime(316): at dalvik.system.NativeStart.main(Native Method)
Never mind, need to add the following for notifcations:
String path=”/sdcard/media/audio/notifications/”;