I'm trying to copy my own alarm tones to Android system by following this explanation and this code.
The problem is that I can't copy the file from my raw resources to the external memory. I've looked other questions here but no answer seems to work for me.
This is my AndroidManifest (I put the WRITE_EXTERNAL_STORAGE outside application tag as stated here):
...
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application...
And this is my code. It runs on a new thread started from onCreate of my main activity. The method "void onFirstTime()" is called from that new thread, and this method calls "void copyRawRingtone(File dstPath, int resId)" repeatedly:
void copyRawRingtone(File dstPath, int resId) {
final String fileName = getResources().getResourceEntryName(resId) + ".mp3";
File dstFile = new File(dstPath, fileName);
if (!dstFile.exists()) {
InputStream srcStream = getResources().openRawResource(resId);
FileOutputStream dstStream = null;
try {
dstFile.createNewFile(); // THIS THROWS THE EXCEPTION
dstStream = new FileOutputStream(dstFile);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = srcStream.read(buffer)) > 0) {
dstStream.write(buffer, 0, bytesRead);
}
// Set the file metadata
final String mimeType = "audio/mpeg";
final String dstAbsPath = dstFile.getAbsolutePath();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DATA, dstAbsPath);
contentValues.put(MediaStore.MediaColumns.TITLE, fileName);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
contentValues.put(MediaStore.Audio.Media.IS_ALARM, true);
contentValues.put(MediaStore.Audio.Media.IS_NOTIFICATION, false);
contentValues.put(MediaStore.Audio.Media.IS_RINGTONE, false);
contentValues.put(MediaStore.Audio.Media.IS_MUSIC, false);
// Add the metadata to the file in the database
Uri contentUri = MediaStore.Audio.Media.getContentUriForPath(dstAbsPath);
Uri newUri = getContentResolver().insert(contentUri, contentValues);
// Tell the media scanner about the new ringtone
MediaScannerConnection.scanFile(
this,
new String[]{newUri.toString()},
new String[]{mimeType},
null
);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (dstStream != null) {
try {
dstStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
try {
srcStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// First time initialization (called in a new thread from onCreate)
void onFirstTime() {
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File dstPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS);
// This returns false but since I run it on an emulator, I don't know how to check if it actually exists:
dstPath.mkdirs();
Field[] rawResources = R.raw.class.getFields();
final ProgressBar installProgress = (ProgressBar) findViewById(R.id.progressBar);
final int length = rawResources.length;
runOnUiThread(new Runnable() {
@Override
public void run() {
installProgress.setMax(length);
}
});
for (int i = 0; i < length; ++i) {
try {
int resId = rawResources[i].getInt(rawResources[i]);
copyRawRingtone(dstPath, resId); // Call the method above
} catch (IllegalAccessException e) {
e.printStackTrace();
}
final int progress = i + 1;
runOnUiThread(new Runnable() {
@Override
public void run() {
installProgress.setProgress(progress);
}
});
}
}
}
The line: dstFile.createNewFile(); is throwing an exception:
java.io.IOException: open failed: ENOENT (No such file or directory)
I don't understand what could be the problem. I'm running this on an emulator: Nexus_S_API_21_armeabi-v7a.
I'm not seeing the storage of the emulator under my PC, so I believe phone emulator storage is not mounted on my PC, therefore I don't think this could be the problem.
At the moment of the exception, those are some variable values:
dstPath="/storage/sdcard/Alarms"
fileName="whistle_alarm.mp3"
dstFile="/storage/sdcard/Alarms/whistle_alarm.mp3"
Since this returns true:
Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)
It seems that the media is mounted and is writable (not MEDIA_MOUNTED_READ_ONLY). So I don't know what could be the problem!! Does anyone has any idea??
EDIT
I added those two lines (I also put one line before and one after to see it in context:
File dstPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS);
final boolean exists = dstPath.exists(); // Added. exists=true
final boolean canWrite = dstPath.canWrite(); // Added. canWrite=true
dstPath.mkdirs();
I also removed:
dstFile.createNewFile(); // THIS THROWS THE EXCEPTION
And it works now!
But I had tested many times without this createNewFile() line and it didn't work. Indeed I added that line to see if I could make it work with it. So I tried again with that line and it works too!
I suspect that the actual solution was to restart the emulator.