5

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.

Community
  • 1
  • 1
Daniel Munoz
  • 1,865
  • 1
  • 14
  • 9
  • 1
    What about putting the files in assets folder ? and access it from there. – theapache64 Mar 03 '15 at 17:14
  • If I do so, can I add those mp3 to the MediaStore to be listed when I start action RingtoneManager.ACTION_RINGTONE_PICKER to pick a tone? Because assets folder is private for my application I think, I don't know if MediaStore will add any files in there to its alarms list. – Daniel Munoz Mar 03 '15 at 17:18
  • 1
    Use dstPath.exists() to check its existence. And after that dstPath.canWrite(). – greenapps Mar 04 '15 at 09:15
  • 1
    `dstFile.createNewFile();`.Remove that statement. The new FileOutputStream will create the file. – greenapps Mar 04 '15 at 09:22
  • @greenapps: both are returning true. But it is working now (it seems that I had to restart the emulator). But thanks anyway, I'll use that to debug if it happens again. – Daniel Munoz Mar 04 '15 at 13:52
  • @greenapps I added that line because the FileOutputStream wasn't creating it. But the problem seems to have been the emulator that needed restart. Now it's working with and without that line. Thanks! – Daniel Munoz Mar 04 '15 at 13:55
  • 1
    `I'll use that to debug if it happens again. `. If you meen exists() and canWrite() than do not omit them. Use them always everywhere. – greenapps Mar 04 '15 at 14:44
  • 1
    Do not always call mkdirs(). Only if .exists() returns false. And check the return value. Do not continue if it fails then. – greenapps Mar 04 '15 at 15:09

0 Answers0