92

When I have a target API of 23 on Android M Preview 3, I cannot seem to acquire the Manifest.permission.WRITE_SETTTINGS permission.

requestPermissions(new String[]{Manifest.permission.WRITE_SETTINGS}, 101);

Request permission doesn't bring up the dialog I would expect, but if I make the following call without this permission,

 RingtoneManager.setActualDefaultRingtoneUri(activity, RingtoneManager.TYPE_RINGTONE, ringUri);

The call will except because I don't have the permission.

I'm not sure where to go from here. Is there a new ringtone API for 23? Or did this permission change just make it impossible for any non-system apps to change the ringtone?

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Justin
  • 3,322
  • 2
  • 22
  • 37

10 Answers10

144

To use WRITE_SETTINGS, based on the docs:

  1. Have the <uses-permission> element in the manifest as normal.

  2. Call Settings.System.canWrite() to see if you are eligible to write out settings.

  3. If canWrite() returns false, start up the ACTION_MANAGE_WRITE_SETTINGS activity so the user can agree there to allow your app to actually write to settings.

In other words, writing to settings is now a double-opt-in (agree to install, agree separately in Settings to allow), akin to device admin APIs, accessibility services, etc.

Also note that I have not tried using these yet — this is based on research that I did yesterday on Android 6.0 changes.

rmtheis
  • 5,992
  • 12
  • 61
  • 78
CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 1
    Thanks Mark! Worked like a charm. https://developer.android.com/preview/features/runtime-permissions.html needs some updating if we are going to have multiple new ways to request permissions. (I had already read your blog before posting, but obviously didn't retain that piece of info when I needed it) – Justin Aug 19 '15 at 13:46
  • This worked, indeed. But for the end user this is a bad approach. Any signs of Google changing this behaviour? – Fhl Sep 09 '15 at 07:58
  • 3
    @Fhl: I do not know why they went this route instead of the regular `dangerous` runtime permission approach they went with other things in Android 6.0. I will be surprised if this changes any time soon. – CommonsWare Sep 09 '15 at 11:08
  • 5
    you can specify your app in the intent like this: `intent.setData(Uri.parse("package:" + Context.getPackageName()));` – Olegas Gončarovas Oct 23 '15 at 17:24
  • 8
    Another thing to note is there seems to be a bug in Android that causes an app that was previously installed where the user gave write permissions in the dialog described above, where the toggle switch will be put in the enabled position yet canWrite returns false.. In order to get canWrite() method to return true the user must toggle the switch off and back on... I see this in development but hopefully it won't be something customers see. – Matt Wolfe Jan 08 '16 at 19:43
  • I don't see a bug report open for the issue mentioned by Matt Wolf so I opened one here: https://code.google.com/p/android/issues/detail?id=204610&thanks=204610&ts=1458555773 – timothyjc Mar 21 '16 at 10:23
  • Is there a way to do this in Espresso? GrantPermissionsRule.grant() will does not work for WRITE_SETTINGS – Kirk Nov 22 '18 at 20:45
  • @Kirk: Purely with Espresso, not that I know of. In principle, you should be able to mix in some `UiAutomator` stuff to navigate the Settings app. That will be fairly fragile, depending on the specific device (since Settings apps are frequently changed by manufacturers). If there's an `adb shell` command that can grant `WRITE_SETTINGS`, you should be able to use that, but I don't know if there is a way to do that. – CommonsWare Nov 22 '18 at 21:37
  • https://www.dev2qa.com/how-to-grant-write-settings-permission-in-android/ – Confused Jan 09 '22 at 23:58
  • My apps not showing in `Modify system settings` – EAS Mar 17 '22 at 10:26
49

In addition to the answer from CommonsWare and the comment from Ogix, here is some dummy code:

private boolean checkSystemWritePermission() {
    boolean retVal = true;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        retVal = Settings.System.canWrite(this);
        Log.d(TAG, "Can Write Settings: " + retVal);
        if(retVal){
            Toast.makeText(this, "Write allowed :-)", Toast.LENGTH_LONG).show();
        }else{
            Toast.makeText(this, "Write not allowed :-(", Toast.LENGTH_LONG).show();
            FragmentManager fm = getFragmentManager();
            PopupWritePermission dialogFragment = new PopupWritePermission();
            dialogFragment.show(fm, getString(R.string.popup_writesettings_title));
        }
    }
    return retVal;
}

The Fragment PopupwritePermission then gives a window where the situation is explained. A click on the OK Button will open the Android System Menu where the Permission can be granted:

private void openAndroidPermissionsMenu() {
    Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
    intent.setData(Uri.parse("package:" + getActivity().getPackageName()));
    startActivity(intent);
}
StephenA
  • 61
  • 6
KP.dev
  • 491
  • 4
  • 3
42

The previous answers are great, I have just little addition for also getting the result for the permission asking.

 public static void youDesirePermissionCode(Activity context){
        boolean permission;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            permission = Settings.System.canWrite(context);
        } else {
            permission = ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_SETTINGS) == PackageManager.PERMISSION_GRANTED;
        }
        if (permission) {
            //do your code
        }  else {
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
                Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
                intent.setData(Uri.parse("package:" + context.getPackageName()));
                context.startActivityForResult(intent, MainActivity.CODE_WRITE_SETTINGS_PERMISSION);
            } else {
                ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.WRITE_SETTINGS}, MainActivity.CODE_WRITE_SETTINGS_PERMISSION);
            }
        }
    }

And then in the Activity:

@SuppressLint("NewApi")
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == MainActivity.CODE_WRITE_SETTINGS_PERMISSION && Settings.System.canWrite(this)){
            Log.d("TAG", "MainActivity.CODE_WRITE_SETTINGS_PERMISSION success");
            //do your code
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == MainActivity.CODE_WRITE_SETTINGS_PERMISSION && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            //do your code
        }
    }
yshahak
  • 4,996
  • 1
  • 31
  • 37
  • I put your code, and it works fine,even the permission granted, but still the custom ringtone is not assigning and still have the Write_Setting permission denied issue. – Zia Ur Rahman Nov 19 '16 at 06:50
  • 4
    ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.WRITE_SETTINGS}, ....); cannot be used. It's a special permission. We can only request this permission with an intent as said in the documentation. Also prior to Marshmello the permission is granted permenantly at install time – Anonymous Jul 17 '17 at 13:08
  • 2
    @yshahak what is your variable `MainActivity.CODE_WRITE_SETTINGS_PERMISSION` ? – Bruno Bieri Jun 03 '18 at 12:12
  • @BrunoBieri yes you right, I omitted that. I will edit my answer so it will be verbose. – yshahak Jun 04 '18 at 06:52
  • So what's `MainActivity.CODE_WRITE_SETTINGS_PERMISSION`? – Brackets Dec 01 '18 at 20:03
  • It's just constant field integer in this snippet. Something like `public static int CODE_WRITE_SETTINGS_PERMISSION = 42;` – yshahak Dec 02 '18 at 07:04
14

This is a complete example:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    if (Settings.System.canWrite(context) {
        // Do stuff here
    }
    else {
        Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS);
        intent.setData(Uri.parse("package:" + getActivity().getPackageName()));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
}
Bruno Bieri
  • 9,724
  • 11
  • 63
  • 92
Daniel Raouf
  • 2,307
  • 1
  • 22
  • 28
9

As of android Marshmellow , you require to use runtime permissions which aims to more security , or use permission when need here is documenatation

and for Write Settings documentation is here

In manifest add

<uses-permission android:name="android.permission.WRITE_SETTINGS" />

In your class

private boolean checkSystemWritePermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        if(Settings.System.canWrite(context))
            return true;
        else 
            openAndroidPermissionsMenu();
    }
    return false;
}

private void openAndroidPermissionsMenu() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
        intent.setData(Uri.parse("package:" + context.getPackageName()));
        context.startActivity(intent);
    }
}

And use it like this

try {
       if (checkSystemWritePermission()) {
            RingtoneManager.setActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE, newUri);
            Toast.makeText(context, "Set as ringtoon successfully ", Toast.LENGTH_SHORT).show();
            }else {
                Toast.makeText(context, "Allow modify system settings ==> ON ", Toast.LENGTH_LONG).show();
            }
        } catch (Exception e) {
            Log.i("ringtoon",e.toString());
            Toast.makeText(context, "unable to set as Ringtoon ", Toast.LENGTH_SHORT).show();
        }
Abhishek Garg
  • 3,092
  • 26
  • 30
7

The permission android.permission.WRITE_SETTINGS is now in the group signature|appop|pre23|preinstalled like android.permission.CHANGE_NETWORK_STATE and android.permission.SYSTEM_ALERT_WINDOW

This means you get it on sdk 22 and below. On newer version you have to be an app operator.

passsy
  • 5,162
  • 4
  • 39
  • 65
6

I have used bellow like..

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        boolean retVal = true;
        retVal = Settings.System.canWrite(this);
        if (retVal == false) {
            if (!Settings.System.canWrite(getApplicationContext())) {

                Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS, Uri.parse("package:" + getPackageName()));
                Toast.makeText(getApplicationContext(), "Please, allow system settings for automatic logout ", Toast.LENGTH_LONG).show();
                startActivityForResult(intent, 200);
            }
        }else {
            Toast.makeText(getApplicationContext(), "You are not allowed to wright ", Toast.LENGTH_LONG).show();
        }
    }

Manifest permission

<uses-permission  android:name="android.permission.WRITE_SETTINGS" tools:ignore="ProtectedPermissions" />
Enamul Haque
  • 4,789
  • 1
  • 37
  • 50
6

Kotlin Version in Simple Steps

Follow these steps:

1. Add the permission's usage element in the manifest.xml normally:

<uses-permission
    android:name="android.permission.WRITE_SETTINGS"
    tools:ignore="ProtectedPermissions" />

2. Where you want to change the settings, check the write access:

if (context.canWriteSettings) {
    // change the settings here ...
} else {
    startManageWriteSettingsPermission()
}

3. Also add these lines of code in case of requesting the permission:

private fun startManageWriteSettingsPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        Intent(
            Settings.ACTION_MANAGE_WRITE_SETTINGS,
            Uri.parse("package:${context.packageName}")
        ).let {
            startActivityForResult(it, REQUEST_CODE_WRITE_SETTINGS_PERMISSION)
        }
    }
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    when (requestCode) {
        REQUEST_CODE_WRITE_SETTINGS_PERMISSION -> {
            if (context.canWriteSettings) {
                // change the settings here ...
            } else {
                Toast.makeText(context, "Write settings permission is not granted!", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

val Context.canWriteSettings: Boolean
    get() = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || Settings.System.canWrite(this)

companion object {
    private const val REQUEST_CODE_WRITE_SETTINGS_PERMISSION = 5
}
aminography
  • 21,986
  • 13
  • 70
  • 74
3

Mention below permission in AndroidManifest.xml

In Activity use below if else for changing setting.

if(Settings.System.canWrite(this)){
    // change setting here
}
else{
    //Migrate to Setting write permission screen. 
    Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
    intent.setData(Uri.parse("package:" + mContext.getPackageName()));
    startActivity(intent);
}
2

In my case i have solved by this way.:

public void checkSystemWriteSettings(Context context) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.System.canWrite(context)) {
                Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS, Uri.parse("package:" + context.getApplicationInfo().packageName));
                startActivity(intent);
            }else
            {
                Settings.System.putInt(context.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS, 10);

            }
        }
    }
Abhishek Dutt
  • 1,308
  • 7
  • 14
  • 24