Skip to content
Elvis Chidera

How To Change The Device Ringtone On All Android Versions

android, audio1 min read

Recent versions of Android come with wonderful features and some (good) restrictions. One of this restriction is the run-time permission that was added in Android Marshmallow. Before Android Marshmallow, permissions were granted at install time rather than at run-time.

While run-time permissions are pretty straightforward to work with, performing some (special) actions require some extra work on the developer and the user path. Example of such special actions is changing the device ringtone or drawing over other apps (like Facebook messenger chat heads).

You will have to take the user out of your app to the settings app for them to be able to enable the permission needed and then they have to go back to your app to continue what they were trying to do.

If all you just want to do is change the device ringtone, this might be too much work for you to do. I created a helper class you can use to easily change the ringtone on a device. It does all the version checking and the special permission handling for you and you can easily extend it to serve your use case.

banner

Before using the class, ensure you have the required WRITE_SETTINGS permission in your AndroidManifest.xml file

AndroidManifest.xml
1<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
3 <uses-permission android:name="android.permission.WRITE_SETTINGS" />
4
5</manifest>

The helper class (RingtoneUtils) has a setRingtone method that does all the work for you, just pass in a context and the URI of the ringtone you want to change to. Here is the helper class:

RingtoneUtils.java
1import android.content.ContentUris;
2import android.content.Context;
3import android.content.Intent;
4import android.media.RingtoneManager;
5import android.net.Uri;
6import android.os.Build;
7import android.provider.MediaStore;
8import android.provider.Settings;
9import android.support.annotation.NonNull;
10import android.support.annotation.RequiresApi;
11import android.util.Log;
12import android.widget.Toast;
13
14public class RingtoneUtils {
15
16 private static final String LOG_TAG = "RingtoneUtils";
17
18 public static boolean setRingtone(@NonNull Context context, @NonNull Uri ringtoneUri) {
19 Log.v(LOG_TAG, "Setting Ringtone to: " + ringtoneUri);
20
21 if (!hasMarshmallow()) {
22 Log.v(LOG_TAG, "On a Lollipop or below device, so go ahead and change device ringtone");
23 setActualRingtone(context, ringtoneUri);
24 return true;
25 }
26 else if(hasMarshmallow() && canEditSystemSettings(context)) {
27 Log.v(LOG_TAG, "On a marshmallow or above device but app has the permission to edit system settings");
28 setActualRingtone(context, ringtoneUri);
29 return true;
30 }
31 else if(hasMarshmallow() && !canEditSystemSettings(context)) {
32 Log.d(LOG_TAG, "On android Marshmallow and above but app does not have permission to" +
33 " edit system settings. Opening the manage write settings activity...");
34 startManageWriteSettingsActivity(context);
35 Toast.makeText(context, "Please allow app to edit settings so your ringtone can be updated", Toast.LENGTH_LONG).show();
36 return false;
37 }
38
39 return false;
40 }
41
42 private static void setActualRingtone(@NonNull Context context, @NonNull Uri ringtoneUri) {
43 RingtoneManager.setActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE, ringtoneUri);
44 }
45
46 @RequiresApi(api = Build.VERSION_CODES.M)
47 private static void startManageWriteSettingsActivity(@NonNull Context context) {
48 Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
49 // Passing in the app package here allows the settings app to open the exact app
50 intent.setData(Uri.parse("package:" + context.getApplicationContext().getPackageName()));
51 // Optional. If you pass in a service context without setting this flag, you will get an exception
52 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
53 context.startActivity(intent);
54 }
55
56 private static boolean hasMarshmallow() {
57 // returns true if the device is Android Marshmallow or above, false otherwise
58 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
59 }
60
61 @RequiresApi(api = Build.VERSION_CODES.M)
62 private static boolean canEditSystemSettings(@NonNull Context context) {
63 // returns true if the app can edit system settings, false otherwise
64 return Settings.System.canWrite(context.getApplicationContext());
65 }
66
67}

Here is an example of the RingtoneUtils being used to change the device ringtone:

ExampleUsage.java
1import android.content.Context;
2import android.database.Cursor;
3import android.net.Uri;
4import android.provider.MediaStore;
5import android.support.annotation.NonNull;
6
7public class ExampleUsage {
8
9 public void setFirstSongOnDeviceAsRingtone(@NonNull Context context) {
10 Uri mediaContentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
11 String[] projection = new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DATA};
12
13 Cursor mediaCursor = context.getContentResolver().query(mediaContentUri, projection, null, null, null);
14
15 if(mediaCursor != null) {
16 if(mediaCursor.getCount() >= 0) {
17 // Move to first song item
18 mediaCursor.moveToPosition(0);
19 long songId = mediaCursor.getLong(mediaCursor.getColumnIndex(MediaStore.Audio.Media._ID));
20 String songUri = mediaCursor.getString(mediaCursor.getColumnIndex(MediaStore.Audio.Media.DATA));
21 Uri songContentUri = MediaStore.Audio.Media.getContentUriForPath(songUri);
22 Uri ringtoneUri = ContentUris.withAppendedId(songContentUri, songId);
23
24 // Use class to change Ringtone
25 RingtoneUtils.setRingtone(context, ringtoneUri);
26 }
27
28 mediaCursor.close();
29 }
30 }
31
32}

The steps are similar if you want to have your app draw over other apps, but this post is more focused on changing the device ringtone.

© 2020 by Elvis Chidera. All rights reserved.