I'm trying to find a way to set a new default ringtone by code from my Android activity.
It works well on Android 13 but on Android 14+ on Android Studio Emulator (for example Nexus 6P API 34) the app crashes. My code is below:
interface Tone{
String PHONE_RINGTONE = "ringtone";
String NOTIFICATION_TONE = "notification";
String ALARM = "alarm";
}
public void setRingtone(String type) {
File exportedDir = new File(getExternalFilesDir(Environment.DIRECTORY_MUSIC)
.toString() + "/" + getPackageName()+ "/" + type + "/");
if (!exportedDir.exists()) {
exportedDir.mkdirs();
}
//delete previous
File[] files = exportedDir.listFiles();
if (files != null) {
for (File file : files)
if (!file.isDirectory())
file.delete();
}
File f = new File(exportedDir, System.currentTimeMillis() + ".mp3");
Uri mUri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.horse);
ContentResolver mCr = getContentResolver();
AssetFileDescriptor soundFile;
try {
soundFile = mCr.openAssetFileDescriptor(mUri, "r");
} catch (FileNotFoundException e) {
soundFile = null;
}
try {
byte[] readData = new byte[1024];
FileInputStream fis = soundFile.createInputStream();
FileOutputStream fos = new FileOutputStream(f);
int i = fis.read(readData);
while (i != -1) {
fos.write(readData, 0, i);
i = fis.read(readData);
}
fos.close();
fos.flush();
} catch (IOException io) {
Toast.makeText(this, io.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, f.getAbsolutePath());
values.put(MediaStore.MediaColumns.TITLE, getString(R.string.app_name));
values.put(MediaStore.MediaColumns.MIME_TYPE, getMIMEType(f.getAbsolutePath()));
values.put(MediaStore.MediaColumns.SIZE, f.length());
values.put(MediaStore.Audio.Media.ARTIST, R.string.app_name);
if (Objects.equals(type, NOTIFICATION_TONE)) {
values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
} else if (Objects.equals(type, ALARM)) {
values.put(MediaStore.Audio.Media.IS_ALARM, true);
} else {
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
}
Uri newUri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
newUri = getContentResolver()
.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
try (OutputStream os = getContentResolver().openOutputStream(newUri)) {
int size = (int) f.length();
byte[] bytes = new byte[size];
try {
BufferedInputStream buf = new BufferedInputStream(Files.newInputStream(f.toPath()));
buf.read(bytes, 0, bytes.length);
buf.close();
os.write(bytes);
os.close();
os.flush();
} catch (IOException e) {
}
} catch (Exception ignored) {
}
}
try {
if (Objects.equals(type, NOTIFICATION_TONE)) {
RingtoneManager.setActualDefaultRingtoneUri(this,
RingtoneManager.TYPE_NOTIFICATION, newUri);
Settings.System.putString(mCr, Settings.System.NOTIFICATION_SOUND, newUri.toString());
showMessage("Done", "The notification tone changed successfully.");
}
else if (Objects.equals(type, ALARM)) {
RingtoneManager.setActualDefaultRingtoneUri(this,
RingtoneManager.TYPE_ALARM, newUri);
Settings.System.putString(mCr, Settings.System.ALARM_ALERT, newUri.toString());
showMessage("Done", "Alarm sound changed successfully.");
} else {
RingtoneManager.setActualDefaultRingtoneUri(this,
RingtoneManager.TYPE_RINGTONE, newUri);
Settings.System.putString(mCr, Settings.System.RINGTONE, newUri.toString());
showMessage("Done", "The ringtone changed successfully.");
}
} catch (Throwable t) {
Toast.makeText(this, t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
}
This code doesn't work on Android 14.
My logcat:
2023-08-02 17:06:41.065 6899-6899/myapp.adfg.com E/AndroidRuntime: FATAL EXCEPTION: main
Process: myapp.adfg.com, PID: 6899
java.lang.IllegalArgumentException: Mutation of _data is not allowed.
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:172)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142)
at android.content.ContentProviderProxy.insert(ContentProviderNative.java:589)
at android.content.ContentResolver.insert(ContentResolver.java:2209)
at android.content.ContentResolver.insert(ContentResolver.java:2171)
at myapp.adfg.com.MainActivity.setRingtone(MainActivity.java:224)
at myapp.adfg.com.MainActivity.lambda$onCreate$3(MainActivity.java:69)
at myapp.adfg.com.MainActivity.$r8$lambda$w64HvE4bNy_ZGRaJSSBx6mEZm-E(Unknown Source:0)
at myapp.adfg.com.MainActivity$$ExternalSyntheticLambda5.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7659)
at android.view.View.performClickInternal(View.java:7636)
at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0)
at android.view.View$PerformClick.run(View.java:30156)
at android.os.Handler.handleCallback(Handler.java:958)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8176)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
The problem is on this line here:
newUri = getContentResolver() .insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
Can someone help me to fix it, please?
I tried to set the sound from the raw folder as a phone ringtone programmatically for the new Android 14+ OS which is incoming this month. I have tested on Android Studio Emulator (for example Nexus 6P API 34). The result is a crash. See the logcat.