12

Situation
One of my fragments accesses the camera. So of course i need to check the permission in my activity first, before i redirect to it. If the user denies the permission the Activity finishes and redirects him to the previous Activity he was in.
Additionally I would like to show a different fragment when the Activity starts and detects that the permission was permanently denied. (The little checkbox "Never ask again" on the android permission dialog)

Problem
I could not find a proper way of detecting, wether the permission was only denied once or if the "Never ask again" checkbox was checked last time and denied the permission permanently.
Keep in mind that I don't want to know that in the onRequestPermissionsResult callback. I need to know in the onCreate of my Activity if the permission currently is granted, denied or permanently denied.

What I tried
ActivityCompat#shouldShowRequestPermissionRationale seems to detect wether the permission has been denied in the past or not. It also returns true if it has been denied only once instead of permanently.

PermissionChecker#checkPermission() didn't seem to notice any difference between permanently and only once denied permission state.

Question
Is there any way of detecting, wether a permission is denied but can still be requested or if it is permanently denied?

Adil Hussain
  • 30,049
  • 21
  • 112
  • 147
Basti
  • 1,117
  • 12
  • 32

6 Answers6

21

Is there any way of detecting, whether a permission is denied but can still be requested or if it is permanently denied?

Unfortunately there is no official API available to detect if permission is permanently denied when user selects "Never ask again"

There is one work around which uses shouldShowRequestPermissionRationale. Create a SharedPreference with default value false and store value returned by shouldShowRequestPermissionRationale in it. Before updating the value, check if the value set was true. If it was true then don't update it.

Whenever you want to check for permission, get the value from SharedPreference and current value returned by shouldShowRequestPermissionRationale. If shouldShowRequestPermissionRationale returns false but value from SharedPreference is true, you can deduce that Never ask again was selected by user.

You can refer to my blog where I have described this approach.

Sagar
  • 23,903
  • 4
  • 62
  • 62
  • How "safe" is it to assume, that this is always works? Does shouldShowRequestPermissionRationale not have a chance to return true straight away with the first request? – Basti Jun 01 '18 at 09:06
  • 2
    @Basti Based on the [documentation](https://developer.android.com/training/permissions/requesting#explain) `shouldShowRequestPermissionRationale ` returns true only if the user has previously denied the request. This should be reliable enough – Sagar Jun 01 '18 at 09:08
  • Great, thanks. Sad that android doesn't offer API for that. Still gonna stress test this solution tho ofc :D – Basti Jun 01 '18 at 09:56
  • @Basti Yeah, that's sad. It would be great if you could let me know if you find some issues during your stress test – Sagar Jun 01 '18 at 09:57
  • Couldn't find any complications. – Basti Jun 04 '18 at 06:42
  • 3
    When I tested this on android 11 it had major problems. If the user went into the settings and selected "Ask Every Time" this approach still returns that as a "Permanently Denied" – Jenn Weingarten Mar 03 '21 at 17:05
  • For anyone wanting a concrete example of how to use shared preferences to detect when the user has denied a permission permanently, see [this](https://github.com/adil-hussain-84/android-runtime-permission-experiments/tree/master/app2) small, minimal app that I have put together. For further discussion, see my answer [here](https://stackoverflow.com/a/73826849/1071320). – Adil Hussain Sep 23 '22 at 11:38
8

shouldShowRequestPermissionRationale returns true or false based on the user preference in previous permission request.

If user just denied permission(not forever) shouldShowRequestPermissionRationale will return true. If denied permission for ever, then returns false. And the trick is that, even the user allowed permission then then shouldShowRequestPermissionRationale will return false.

So we can combine both the conditions to get the never ask again chosen or not.

So if user not allowed permission and shouldShowRequestPermissionRationale returns false then it means user opt for never ask again for the permission.

vishnu benny
  • 998
  • 1
  • 11
  • 15
0

You can check the permission by,

if (permissionStatus.getBoolean(Manifest.permission.CALL_PHONE, false)){}

Normally I check the permission using following,

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {

   if (ActivityCompat.shouldShowRequestPermissionRationale(TrackActivity.this, Manifest.permission.CALL_PHONE)) {
                ActivityCompat.requestPermissions(TrackActivity.this, new String[]{Manifest.permission.CALL_PHONE}, EXTERNAL_STORAGE_PERMISSION_CONSTANT);
       } else if (permissionStatus.getBoolean(Manifest.permission.CALL_PHONE, false)) {

       } else {
                ActivityCompat.requestPermissions(TrackActivity.this, new String[]{Manifest.permission.CALL_PHONE}, EXTERNAL_STORAGE_PERMISSION_CONSTANT);
       }
            SharedPreferences.Editor editor = permissionStatus.edit();
            editor.putBoolean(Manifest.permission.CALL_PHONE, true);
            editor.apply();
   }
Subin Babu
  • 1,515
  • 2
  • 24
  • 50
0

You can use Dexter library, A library that makes handling android permissions easier. I solved the problem this way:

private void checkUserPermission() {
    Dexter.withActivity(this)
            .withPermissions(
                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.CALL_PHONE,
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_COARSE_LOCATION,
                    Manifest.permission.CAMERA
            ).withListener(new MultiplePermissionsListener() {
        @Override
        public void onPermissionsChecked(MultiplePermissionsReport report) {
            //check if all permission are granted
            if (report.areAllPermissionsGranted()) {
                initSocket();
            } else {
                List<PermissionDeniedResponse> responses = report.getDeniedPermissionResponses();
                StringBuilder permissionsDenied = new StringBuilder("Permissions denied: ");
                for (PermissionDeniedResponse response : responses) {
                    permissionsDenied.append(response.getPermissionName()).append(" ") ;
                }
                showInfoMessageToast(permissionsDenied.toString());
            }

            if (report.isAnyPermissionPermanentlyDenied()) {
                //permission is permanently denied navigate to user setting
                AlertDialog.Builder dialog = new AlertDialog.Builder(HomeActivity.this)
                        .setTitle("Need Permissions")
                        .setMessage("This application need to use some permissions, " +
                                "you can grant them in the application settings.")
                        .setPositiveButton("GOTO SETTINGS", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                dialogInterface.cancel();
                                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                Uri uri = Uri.fromParts("package", getPackageName(), null);
                                intent.setData(uri);
                                startActivityForResult(intent, 101);
                            }
                        })
                        .setNegativeButton("CANCEL", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                dialogInterface.cancel();
                            }
                        });
                dialog.show();

            }
        }

        @Override
        public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {
            token.continuePermissionRequest();
        }
    })
            .onSameThread()
            .check();

}

that is after creating an import this way

import com.karumi.dexter.Dexter;

Tweaking that to what you want to achieve, should solve your problem.

-1

This library works well: https://github.com/permissions-dispatcher/PermissionsDispatcher

Also this medium article is useful: Detecting wether a permission can be requested or is permanently denied

MMG
  • 3,226
  • 5
  • 16
  • 43
-3

To detect that the user is permanently denied the permission or not we don't need any extra logic just use it

if (ActivityCompat.shouldShowRequestPermissionRationale(UserEditProfileActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE)){
                // not permanently denied 
            }
            else {
               // permanently denied 
            }

that's it.

Arup
  • 54
  • 1
  • 8