212

I was going through the official doc about the new Permissions model in Android M. It talks about the shouldShowRequestPermissionRationale() function which returns true if the app has requested this permission previously and the user denied the request. If the user turned down the permission request in the past and chose the Don't ask again option, this method returns false.

But how can we differentiate between the following two cases?

Case 1: The app doesn't have a permission and the user has not been asked for the permission before. In this case, shouldShowRequestPermissionRationale() will return false because this is the first time we're asking the user.

Case 2: The user has denied the permission and selected "Don't ask again", in this case too shouldShowRequestPermissionRationale() will return false.

I would want to send the user to the App's settings page in Case 2. How do i go about differentiating these two cases?

akshayt23
  • 2,793
  • 3
  • 21
  • 29
  • 1
    The accepted answer is good. Just as an alternative you could also use a shared pref to know if the app has requested the permission before. Just throwing that out there in case it's more applicable for someone else's situation. – Rockin4Life33 May 07 '16 at 18:47
  • 7
    There is a case 3 as well: The user has been asked for and granted/denied the permission, but has used the permission settings to revert back to "ask every time". Testing shows `shouldShowRequestPermissionRationale()` returns false in this case, which will hurt any code relying on a "have I asked before" flag. – Logan Pickup Feb 28 '18 at 06:53
  • here is a google sample showing the best practices in `permissions` on Android. https://github.com/android/permissions-samples – itabdullah Jan 22 '20 at 17:52
  • 2
    @itabdullah Google's sample code is useless since they didn't even consider the highly likely usecase of "did the user perma-deny the permission last time". :-/ typical – Someone Somewhere Jul 24 '20 at 20:41

13 Answers13

229

After M Preview 1, if the dialog is displayed for the first time, there is no Never ask again checkbox.

If the user denies the permission request, there will be a Never ask again checkbox in the permission dialog the second time permission is requested.

So the logic should be like this:

  1. Request permission:

    if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
    } else {
        //Do the stuff that requires permission...
    }
    
  2. Check if the permission was denied or granted in onRequestPermissionsResult.

    If the permission was denied previously, this time there will be a Never ask again checkbox in the permission dialog.

    Call shouldShowRequestPermissionRationale to see if the user checked Never ask again. shouldShowRequestPermissionRationale method returns false only if the user selected Never ask again or device policy prohibits the app from having that permission:

    if (grantResults.length > 0){
        if(grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            //Do the stuff that requires permission...
        }else if (grantResults[0] == PackageManager.PERMISSION_DENIED){
            // Should we show an explanation?
            if (ActivityCompat.shouldShowRequestPermissionRationale(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                //Show permission explanation dialog...
            }else{
                //Never ask again selected, or device policy prohibits the app from having that permission.
                //So, disable that feature, or fall back to another situation...
            }
        }
    }
    

So, you won't have to track if a user checked Never ask again or not.

Tim
  • 41,901
  • 18
  • 127
  • 145
CanC
  • 3,295
  • 1
  • 14
  • 15
  • 71
    One point of clarification, shouldShowRequestPermissionRationale() willl also return false if the user has has never been asked asked for the permission (ie the first time the application is run). You would not run into that case if you follow the logic of the example provided. But the wording, under 2 is a little misleading. – Ben Mar 23 '16 at 16:15
  • so basically `shouldShowRequestPermissionRationale` is for explaning in detail what features will be available for the user after allowing the permission and it will return true if the "do not show again" input was checked? is that correct? – jmsalcido Jun 09 '16 at 02:21
  • 1
    @Canc very nice explanation. Thanks :) – AndoAiron Jul 29 '16 at 06:52
  • 1
    Best explanation ever!! – prasanthMurugan Oct 14 '16 at 09:03
  • 17
    I'm not sure, this seems flawed. How are we supposed to know if it is the first time the user gets asked? I have to track if the user got asked, and if he did, then I have to reverse the logic. Doesn't make any sense to me. – Daniel F Mar 13 '17 at 15:38
  • 1
    It always return false. – K.Sopheak May 25 '17 at 07:49
  • 1
    If there is a possibility that the permission may not be intuitive, chances are that one the very first occasion itself there is a requirement to show a popup. In this case, this flow won't work and there doesn't seem to be an option except to do some sort of local tracking. But yes, apt example for most cases. – Rajat Sharma May 26 '17 at 11:53
  • 7
    I think it's worth noting that where you are passing `context` in `ActivityCompat.shouldShowRequestPermissionRationale(...)` the parameter is actually of type `Activity`. May not affect you all but in my case it does. – aProperFox Sep 05 '17 at 23:57
  • The above behavior is *not* what I'm seeing on my Pixel. If I deny permission once, whether or not I select "Don't show again", then `shouldShowRequestPermissionRationale()` still returns true. So I can't tell the difference between "Denied once" and "Denied and Don't show again". – Yervant Nov 24 '17 at 16:05
  • This doesn't seem to be correct. I have followed the same logic and I get an unexpected result on Nexus 5X – mr5 Mar 21 '18 at 09:49
  • This is helped me to understand permission logic. – Harshil Dholakiya Apr 07 '18 at 13:29
  • 14
    This android logic is so darn stupid! It FORCES me to call the `should` in the callback AND save its counter-value in NVM just to know if I need to prompt the request again the next time the app opens! ... wow (facepalm) ... was it too difficult to make only one call returning a status enumeration?? – Shockwaver Sep 05 '18 at 14:40
  • Is it just me or does the "Never ask again" logic conflict with the [example in the documentation](https://developer.android.com/training/permissions/requesting#java), which continues to `requestPermission` if `shouldShowRequestPermissionRationale` is `false`? By that logic, if the user has opted to "Never ask again", then the example would continue to `requestPermission`, which we wouldn't want since the user doesn't want us to ask again. Is the example in the documentation a bad example? – JHowzer Nov 08 '18 at 00:21
  • 13
    I think this is a big fail by Google. The official documentation states that the shouldShowRequestPermissionRationale() should be called before checking the permissions (see https://developer.android.com/training/permissions/requesting#explain), but all answers in StackOverflow call it in onRequestPermissionResult() in order to distinguish whether the user clicked "Never ask again" or not. – Miloš Černilovský Apr 02 '19 at 07:21
  • I don't think this answer addresses the question properly. It does give an alternative flow, but in no way allows to distinguish between the question's case A and B without before requesting the permission. – Jorge Galvão May 23 '20 at 21:58
  • @Ben I have tested the behaviour on a Samsung A10 and the method returns false the first time on a fresh install. I would expect it to return true, but it seems that's not the case here. Would it be a vendor-specific issue? – argenkiwi Mar 23 '21 at 02:43
  • `shouldShowRequestPermissionRationale` also returns `false` if user clicks back on the navigation menu – Iglesias Leonardo Jan 11 '23 at 20:29
30

I had the same problem and I figured it out. To make life much simpler, I wrote an util class to handle runtime permissions.

public class PermissionUtil {
    /*
    * Check if version is marshmallow and above.
    * Used in deciding to ask runtime permission
    * */
    public static boolean shouldAskPermission() {
        return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M);
    }
private static boolean shouldAskPermission(Context context, String permission){
        if (shouldAskPermission()) {
            int permissionResult = ActivityCompat.checkSelfPermission(context, permission);
            if (permissionResult != PackageManager.PERMISSION_GRANTED) {
                return true;
            }
        }
        return false;
    }
public static void checkPermission(Context context, String permission, PermissionAskListener listener){
/*
        * If permission is not granted
        * */
        if (shouldAskPermission(context, permission)){
/*
            * If permission denied previously
            * */
            if (((Activity) context).shouldShowRequestPermissionRationale(permission)) {
                listener.onPermissionPreviouslyDenied();
            } else {
                /*
                * Permission denied or first time requested
                * */
if (PreferencesUtil.isFirstTimeAskingPermission(context, permission)) {
                    PreferencesUtil.firstTimeAskingPermission(context, permission, false);
                    listener.onPermissionAsk();
                } else {
                    /*
                    * Handle the feature without permission or ask user to manually allow permission
                    * */
                    listener.onPermissionDisabled();
                }
            }
        } else {
            listener.onPermissionGranted();
        }
    }
/*
    * Callback on various cases on checking permission
    *
    * 1.  Below M, runtime permission not needed. In that case onPermissionGranted() would be called.
    *     If permission is already granted, onPermissionGranted() would be called.
    *
    * 2.  Above M, if the permission is being asked first time onPermissionAsk() would be called.
    *
    * 3.  Above M, if the permission is previously asked but not granted, onPermissionPreviouslyDenied()
    *     would be called.
    *
    * 4.  Above M, if the permission is disabled by device policy or the user checked "Never ask again"
    *     check box on previous request permission, onPermissionDisabled() would be called.
    * */
    public interface PermissionAskListener {
/*
        * Callback to ask permission
        * */
        void onPermissionAsk();
/*
        * Callback on permission denied
        * */
        void onPermissionPreviouslyDenied();
/*
        * Callback on permission "Never show again" checked and denied
        * */
        void onPermissionDisabled();
/*
        * Callback on permission granted
        * */
        void onPermissionGranted();
    }
}

And the PreferenceUtil methods are as follows.

public static void firstTimeAskingPermission(Context context, String permission, boolean isFirstTime){
SharedPreferences sharedPreference = context.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE;
 sharedPreference.edit().putBoolean(permission, isFirstTime).apply();
 }
public static boolean isFirstTimeAskingPermission(Context context, String permission){
return context.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE).getBoolean(permission, true);
}

Now, all you need is to use the method checkPermission with proper arguments.

Here is an example,

PermissionUtil.checkPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    new PermissionUtil.PermissionAskListener() {
                        @Override
                        public void onPermissionAsk() {
                            ActivityCompat.requestPermissions(
                                    thisActivity,
              new String[]{Manifest.permission.READ_CONTACTS},
                            REQUEST_EXTERNAL_STORAGE
                            );
                        }
@Override
                        public void onPermissionPreviouslyDenied() {
                       //show a dialog explaining permission and then request permission
                        }
@Override
                        public void onPermissionDisabled() {
Toast.makeText(context, "Permission Disabled.", Toast.LENGTH_SHORT).show();
                        }
@Override
                        public void onPermissionGranted() {
                            readContacts();
                        }
                    });

Case 1: The app doesn't have a permission and the user has not been asked for the permission before. In this case, shouldShowRequestPermissionRationale() will return false because this is the first time we're asking the user.

Case 2: The user has denied the permission and selected "Don't ask again", in this case too shouldShowRequestPermissionRationale() will return false.

I would want to send the user to the App's settings page in Case 2. How do i go about differentiating these two cases?

You'll get callback on onPermissionAsk for case 1, and onPermissionDisabled for case 2.

Happy coding :)

muthuraj
  • 1,033
  • 15
  • 22
13

The way I understand it, shouldShowRequestPermissionRationale() runs a number of use cases under the hood, and notifies the app whether or not to show an explanation on the permissions being requested.

The idea behind the Run Time permissions is that most of the time, the user will say Yes to the permission request. That way the user will have to do only one click. Of course the request should be used in the correct context - i.e. asking for the Camera permission when the "Camera" button is pressed.

If the user denies the request, but after some time comes around and presses the "Camera" button again, shouldShowRequestPermissionRationale() will return true, so the app can show some meaningful explanation why the permission is requested, and why the app won't work properly without it. Normally you would show in that dialog window a button to deny again/decide later, and a button to grant the permissions. The grant permissions button in the rationale dialog, should start the permission request again. This time the user will also have a "Never show again" checkbox. Should he decide to select it, and deny the permission again, it would notify the Android system that the user and the app are not on the same page. That action would have two consequences - shouldShowRequestPermissionRationale() will always return false, and the requestPermissions() method will not show any dialog, but will directly return denied to the onRequestPermissionsResult callback.

But there is also another possible scenario where onRequestPermissionsResult could be used. For example some devices may have a device policy that disables the camera (working for CIA, DARPA, etc). On these devices, onRequestPermissionsResult will always return false, and the requestPermissions() method will silently deny the request.

That's what I gathered by listening to the podcast with Ben Poiesz - a product manager on the Android framework.
http://androidbackstage.blogspot.jp/2015/08/episode-33-permission-mission.html

Shumoapp
  • 1,489
  • 16
  • 15
8

UPDATE

I believe that CanC's answer below is the correct one that should be followed. The only way to know for sure is to verify this in the onRequestPermissionResult callback using shouldShowPermissionRationale.

==

My original answer:

The only way that I have found is to keep track on your own of whether this is the first time or not (e.g. using shared preferences). If it's not the first time, then use shouldShowRequestPermissionRationale() to differentiate.

Also see: Android M - check runtime permission - how to determine if the user checked "Never ask again"?

Community
  • 1
  • 1
Alex Florescu
  • 5,096
  • 1
  • 28
  • 49
  • 1
    Yes even I agree that CanC's method is the one that should be followed. I'm going to mark it as the accepted answer. – akshayt23 Jan 11 '16 at 10:36
5

Just post another option, if anyone may feel like. You can use EasyPermissions which was provided by Google itself, to, as said, "Simplify Android M system permissions".

Then you don't have to handle shouldShowRequestPermissionRationale directly.

Wei WANG
  • 1,748
  • 19
  • 23
  • why i didn't see this project prevoiusly :) – Vlad Oct 04 '17 at 13:59
  • 1
    The problem with EasyPermissions stays almost the same. Asking `permissionPermanentlyDenied` internally just calls `shouldShowPermissionsRationale` and returns `true` in the case when the user never was requested to grant permissions. – hgoebl Oct 21 '18 at 09:46
4

We can do it by this way?

@Retention(RetentionPolicy.SOURCE)
@IntDef({GRANTED, DENIED, NEVER})
public @interface PermissionStatus {
}

public static final int GRANTED = 0;
public static final int DENIED = 1;
public static final int NEVER = 2;

@PermissionStatus
public static int getPermissionStatus(Activity activity, String permission) {
    if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
        return DENIED;
    } else {
        if (ActivityCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED) {
            return GRANTED;
        } else {
            return NEVER;
        }
    }
}
Cyph3rCod3r
  • 1,978
  • 23
  • 33
  • 2
    Unfortunately, this code does not distinguish between a situation where the permission was never requested before and where "never request again" was checked. – Gichamba Sep 25 '18 at 05:44
  • you should use the combination of this + the permission helper class to check if the permission is granted or not. – Cyph3rCod3r Oct 01 '18 at 07:26
  • This is great, the best answer, correct and precise. – Borzh Jun 30 '23 at 13:00
3

May be useful for someone:--

What I have noticed is, if we check the shouldShowRequestPermissionRationale() flag in to onRequestPermissionsResult() callback method, it shows only two states.

State 1:-Return true:-- Any time user clicks Deny permissions (including the very first time).

State 2:-Returns false :- if user selects “never asks again".

Link for detailed working example.

Community
  • 1
  • 1
Nicks
  • 16,030
  • 8
  • 58
  • 65
  • 8
    it returns **false** for the very first time. not true – JoM Mar 22 '16 at 00:47
  • Yes, that's what I mentioned, if you check the flag in onRequestPermissionsResult() callback method, it will have two states only, specifically in this callback. – Nicks Mar 22 '16 at 07:31
  • 2
    Unfortunately, **shouldShowRequestPermissionRationale** always returns false - regardless if the user ever denied the permission or not. – IgorGanapolsky Dec 05 '16 at 22:09
2

Check this implementation. is working pretty good for me. basically you check the permissions in the checkPermissions() method passing a list of permissions. You check the result of the permission request on onRequestPermissionsResult(). The implementation lets u address both case when user selects "never ask again" or not. In this implementation, in case se selects "never ask again", the dialog has an option to take him to the App Settings Activity.

All this code is inside my fragment. I was thinking that would be better to create a specialised class to do this, like a PermissionManager, but i'm not sure about it.

/**
     * responsible for checking if permissions are granted. In case permissions are not granted, the user will be requested and the method returns false. In case we have all permissions, the method return true.
     * The response of the request for the permissions is going to be handled in the onRequestPermissionsResult() method
     * @param permissions list of permissions to be checked if are granted onRequestPermissionsResult().
     * @param requestCode request code to identify this request in
     * @return true case we already have all permissions. false in case we had to prompt the user for it.
     */
    private boolean checkPermissions(List<String> permissions, int requestCode) {
        List<String> permissionsNotGranted = new ArrayList<>();
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(getActivity(), permission) != PackageManager.PERMISSION_GRANTED)
                permissionsNotGranted.add(permission);
        }

        //If there is any permission we don't have (it's going to be in permissionsNotGranted List) , we need to request.
        if (!permissionsNotGranted.isEmpty()) {
            requestPermissions(permissionsNotGranted.toArray(new String[permissionsNotGranted.size()]), requestCode);
            return false;
        }
        return true;
    }

    /**
     * called after permissions are requested to the user. This is called always, either
     * has granted or not the permissions.
     * @param requestCode  int code used to identify the request made. Was passed as parameter in the
     *                     requestPermissions() call.
     * @param permissions  Array containing the permissions asked to the user.
     * @param grantResults Array containing the results of the permissions requested to the user.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case YOUR_REQUEST_CODE: {
                boolean anyPermissionDenied = false;
                boolean neverAskAgainSelected = false;
                // Check if any permission asked has been denied
                for (int i = 0; i < grantResults.length; i++) {
                    if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                        anyPermissionDenied = true;
                        //check if user select "never ask again" when denying any permission
                        if (!shouldShowRequestPermissionRationale(permissions[i])) {
                            neverAskAgainSelected = true;
                        }
                    }
                }
                if (!anyPermissionDenied) {
                    // All Permissions asked were granted! Yey!
                    // DO YOUR STUFF
                } else {
                    // the user has just denied one or all of the permissions
                    // use this message to explain why he needs to grant these permissions in order to proceed
                    String message = "";
                    DialogInterface.OnClickListener listener = null;
                    if (neverAskAgainSelected) {
                        //This message is displayed after the user has checked never ask again checkbox.
                        message = getString(R.string.permission_denied_never_ask_again_dialog_message);
                        listener = new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                //this will be executed if User clicks OK button. This is gonna take the user to the App Settings
                                startAppSettingsConfigActivity();
                            }
                        };
                    } else {
                        //This message is displayed while the user hasn't checked never ask again checkbox.
                        message = getString(R.string.permission_denied_dialog_message);
                    }
                    new AlertDialog.Builder(getActivity(), R.style.AlertDialogTheme)
                            .setMessage(message)
                            .setPositiveButton(getString(R.string.label_Ok), listener)
                            .setNegativeButton(getString(R.string.label_cancel), null)
                            .create()
                            .show();
                }
            }
            break;
            default:
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    /**
     * start the App Settings Activity so that the user can change
     * settings related to the application such as permissions.
     */
    private void startAppSettingsConfigActivity() {
        final Intent i = new Intent();
        i.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        i.addCategory(Intent.CATEGORY_DEFAULT);
        i.setData(Uri.parse("package:" + getActivity().getPackageName()));
        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
        i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
        getActivity().startActivity(i);
    }
Thiago Saraiva
  • 181
  • 1
  • 10
2

If anyone is interested in a Kotlin solution, I refactored @muthuraj answer to be in Kotlin. Also modernized it a bit to have a completion block instead of listeners.

PermissionUtil

object PermissionUtil {
    private val PREFS_FILE_NAME = "preference"

    fun firstTimeAskingPermission(context: Context, permission: String, isFirstTime: Boolean) {
        val sharedPreference = context.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE)
        sharedPreference.preferences.edit().putBoolean(permission,
                isFirstTime).apply()
    }

    fun isFirstTimeAskingPermission(context: Context, permission: String): Boolean {
        val sharedPreference = context.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE)
        return sharedPreference.preferences.getBoolean(permission,
                true)
    }
}

PermissionHandler

enum class CheckPermissionResult {
    PermissionAsk,
    PermissionPreviouslyDenied,
    PermissionDisabled,
    PermissionGranted
}

typealias PermissionCheckCompletion = (CheckPermissionResult) -> Unit


object PermissionHandler {

    private fun shouldAskPermission(context: Context, permission: String): Boolean {
        return ContextCompat.checkSelfPermission(context,
                permission) != PackageManager.PERMISSION_GRANTED
    }

    fun checkPermission(context: Context, permission: String, completion: PermissionCheckCompletion) {
        // If permission is not granted
        if (shouldAskPermission(context, permission)) {
            //If permission denied previously
            if ((context as Activity).shouldShowRequestPermissionRationale(permission)) {
                completion(CheckPermissionResult.PermissionPreviouslyDenied)
            } else {
                // Permission denied or first time requested
                if (PermissionUtil.isFirstTimeAskingPermission(context,
                                permission)) {
                    PermissionUtil.firstTimeAskingPermission(context,
                            permission,
                            false)
                    completion(CheckPermissionResult.PermissionAsk)
                } else {
                    // Handle the feature without permission or ask user to manually allow permission
                    completion(CheckPermissionResult.PermissionDisabled)
                }
            }
        } else {
            completion(CheckPermissionResult.PermissionGranted)
        }
    }
}

Implementation

PermissionHandler.checkPermission(activity,
                    Manifest.permission.CAMERA) { result ->
                when (result) {
                    CheckPermissionResult.PermissionGranted -> {
                        // openCamera()
                    }
                    CheckPermissionResult.PermissionDisabled -> {
                        // displayAlert(noPermissionAlert)
                    }
                    CheckPermissionResult.PermissionAsk -> {
                        // requestCameraPermissions()
                    }
                    CheckPermissionResult.PermissionPreviouslyDenied -> {
                        // displayAlert(permissionRequestAlert)
                    }
                }
            }
bmjohns
  • 6,344
  • 1
  • 33
  • 39
  • 2
    Don't pass a Context in PermissionHandler.checkPermission() and then cast it as an Activity later on. A Context is not always an Activity – Nino DELCEY Jan 21 '21 at 09:30
0
public void requestPermission(View view){
        if(ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
            if(ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,Manifest.permission.ACCESS_FINE_LOCATION)){

//The Alert Dialog before asking for the second time to help the user understand  why he needs to give permission.

                AlertDialog alert = new AlertDialog.Builder(this).setMessage("Without the permissions you cannot run the application")
                        .setCancelable(false)
                        .setPositiveButton("Okay, I understand", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                ActivityCompat.requestPermissions(MainActivity.this,
                                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_FINE_LOCATION);
                            }
                        }).setNegativeButton("No, Exit the App", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        System.exit(2);
                    }
                }).create();
                alert.setTitle("ALERTY");
                alert.show();
//End of the alert Dialog
            }
            else{
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_FINE_LOCATION);
            }
        }
        else {
            textView.setText("Permission Is Already Granted");
        }
    }
    /*
     The shouldShowRequestPermissionRationale() function returns true if the app has requested this permission
     previously and the user denied the request.If the user turned down the permission request in the past and chose
     the Don't ask again option, this method returns false.
      */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if(requestCode == REQUEST_FINE_LOCATION) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                textView.setText("Hooray! on Request Permissions Granted");
            }
            else{
                //Since the user has chosen the don't ask again option,
                if(!ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION)){

//Alert Dialog that will take user to settings where he can manually give the permissions
                    AlertDialog alert = new AlertDialog.Builder(this).setMessage("You have permanently disabled the permission ")
                            .setPositiveButton("Go to Settings", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                             openSettings();
                        }
                    }).setNegativeButton("Don't Go",null).setCancelable(false).create();
                  alert.setTitle("Give permission manually");
                  alert.show();
 // End of the Alert Dialog
                }
                else{
                    textView.setText("Permission has been denied the 1st time");
                }
            }
        }
    }

This is the openSettings method.

    public void openSettings(){
        Intent intent = new Intent();

        Uri uri = Uri.fromParts("package",this.getPackageName(),null);
        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(uri);
        startActivity(intent);
    }
Eva
  • 4,397
  • 5
  • 43
  • 65
Arpit Anand
  • 347
  • 2
  • 17
  • I have created the intent so that the user will be redirected to the settings page if he had previously checked on the **Don't show again** button where he can give the permissions manually. – Arpit Anand Sep 20 '20 at 06:58
0

Correct usage of shouldShowRequestPermissionRationale is the on onRequestPermissionsResult.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
    <androidx.appcompat.widget.LinearLayoutCompat
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity"
        android:gravity="center">
    
        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/btn_camera"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Camera"
            android:textAllCaps="false"
            android:background="@color/purple_200"
            android:layout_marginTop="20dp"
            >
        </androidx.appcompat.widget.AppCompatButton>
        <androidx.appcompat.widget.AppCompatButton
            android:id="@+id/btn_storage"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Storage"
            android:textAllCaps="false"
            android:background="@color/purple_200"
            android:layout_marginTop="30dp"
            >
        </androidx.appcompat.widget.AppCompatButton>
    
    </androidx.appcompat.widget.LinearLayoutCompat>
enter code here

MainActivity.kt

package com.example.myapplication
import android.Manifest
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
class MainActivity : AppCompatActivity() {
    private val TAG = "MainActivity"
    lateinit var btnCamera: Button
    private val cameraRequestCode = 100
    lateinit var btnStorage: Button
    private val storageRequestCode = 200
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btnCamera = findViewById(R.id.btn_camera)
        btnStorage = findViewById(R.id.btn_storage)
        btnCamera.setOnClickListener {
            checkPermission(android.Manifest.permission.CAMERA, cameraRequestCode)
        }
        btnStorage.setOnClickListener {
            checkPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE, storageRequestCode)
        }
    }

    private fun checkPermission(permissionName: String, requestCode: Int) {
        /**
         * if the permission is given means it will give the  permissionNumber = 0
         * if the permission is not  given means it will give the  permissionNumber =-1
         * It s same as we are checking for PackageManager.PERMISSION_DENIED =-1 & PackageManager.GRANTED=0
         */
        val permissionNumber: Int =
            ContextCompat.checkSelfPermission(this@MainActivity, permissionName)
        if (permissionNumber == PackageManager.PERMISSION_GRANTED) {

        } else if (permissionNumber == PackageManager.PERMISSION_DENIED) {
            askpermission(permissionName, requestCode, permissionNumber)

        }

    }

    private fun askpermission(permissionName: String, permissionCode: Int, permissionNumner: Int) {
        ActivityCompat.requestPermissions(
            this@MainActivity,
            arrayOf(permissionName),
            permissionCode
        )
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == cameraRequestCode) {
            if (permissions.size > 0) {
                if (permissions[0].toString().equals(Manifest.permission.CAMERA, ignoreCase = true)) {
                    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        Toast.makeText(applicationContext,"Permission Granted",Toast.LENGTH_SHORT).show()

                    }else{
                        if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
                           var permissionDeniedStatus= checkDeniedPermissionStatus(permissions[0]);
                            if(permissionDeniedStatus){
                                /**
                                 * Permission Denied
                                 */
                                Toast.makeText(applicationContext,"Permission Denied",Toast.LENGTH_SHORT).show()
                            }else{
                                /**
                                 * Permission Denied and Selected Don t ask again.
                                 */
                                showDialog("Permission Denied","Permission Denied Permanently\nOpen Setting to allow")
                            }
                        }
                    }
                }
            }
        }else  if (requestCode == storageRequestCode) {
            if(permissions[0].toString().equals(Manifest.permission.READ_EXTERNAL_STORAGE, ignoreCase = true)){
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        Toast.makeText(applicationContext,"Permission Granted",Toast.LENGTH_SHORT).show()
                    }
                }else{
                    if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
                        var permissionDeniedStatus= checkDeniedPermissionStatus(permissions[0]);
                        if(permissionDeniedStatus){
                            /**
                             * Permission Denied
                             */
                            Toast.makeText(applicationContext,"Permission Denied",Toast.LENGTH_SHORT).show()
                        }else{
                            /**
                             * Permission Denied and Selected Don t ask again.
                             */
                            showDialog("Permission Denied","Permission Denied Permanently\nOpen Setting to allow")
                        }
                    }
                }
            }
        }
    }

    private fun checkDeniedPermissionStatus(permissionName: String) :Boolean{
        val permissionDeniedStatus: Boolean = ActivityCompat.shouldShowRequestPermissionRationale(this@MainActivity, permissionName)
        return permissionDeniedStatus
    }

    private fun showDialog(title: String, message: String) {
        val builder = AlertDialog.Builder(this)
        builder.setTitle(title)
        builder.setMessage(message)
        builder.setPositiveButton(android.R.string.yes) { dialog, which ->

        }

        builder.setNegativeButton(android.R.string.no) { dialog, which ->

        }
        builder.show()
    }
}
vinay shetty
  • 895
  • 1
  • 9
  • 15
0

Google rejected my app because I used one of the answers. So, here is my version:

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build;

import androidx.core.app.ActivityCompat;

public class PermissionUtil {

    static final String PREFS_NAME = "AppsPermissions";

    private static boolean shouldAskPermission(Context context, String permission) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            int permissionResult = ActivityCompat.checkSelfPermission(context, permission);
            if (permissionResult != PackageManager.PERMISSION_GRANTED) {
                return true;
            }
        }
        return false;
    }

    public static void checkPermission(Activity activity, String permission, PermissionAskListener listener) {
        // If permission is not granted
        if (shouldAskPermission(activity, permission)) {
            // If permission denied previously without "Don't ask again"
            if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
                listener.onPermissionAsk();
            } else {
                SharedPreferences sharedPreference = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
                if (sharedPreference.getBoolean(permission, true)) {
                    // Permission first time requested.
                    sharedPreference.edit().putBoolean(permission, false).apply();
                    listener.onPermissionAsk();
                }
                else {
                    // Handle the feature without permission or ask user to manually allow permission
                    listener.onPermissionDenied();
                }
            }
        }
        else {
            listener.onPermissionGranted();
        }
    }

    /// Callback on checking permission.
    public interface PermissionAskListener {
        /// Callback to ask permission
        void onPermissionAsk();

        /// Callback on permission denied
        void onPermissionDenied();

        /// Callback on permission granted
        void onPermissionGranted();
    }
}
Borzh
  • 5,069
  • 2
  • 48
  • 64
-1

This code asks user to ask permission during runtime, if user allows, it execute result method, if user deny, it ask again with discription with user deny (it ask again with instructions), but if user choose never ask again. it handles never ask again, display open settings option with instructions.

public String storagePermissions = Manifest.permission.READ_EXTERNAL_STORAGE;   
private static final int REQUEST_ACCESS =101;  

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      if(checkSelfPermission(storagePermissions)== PackageManager.PERMISSION_GRANTED){
          result();    // result  is your block of code 
      }else {
          requestPermissions(new String[]{storagePermissions},REQUEST_ACCESS);
      }

    }
    else{
        result();    //so if user is lower than api verison M, no permission is requested
    } 

}

 private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
    new AlertDialog.Builder(MainActivity.this)
            .setMessage(message)
            .setTitle("Hi User..")
            .setPositiveButton("Ok", okListener)
            .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {        //idea calling showMessage funtion again
                    Snackbar mySnackbar = Snackbar.make( findViewById(R.id.coordinatorlayout),"You Press Cancel.. ", Snackbar.LENGTH_INDEFINITE);
                    mySnackbar.setAction("Exit", new cancelButton());
                    mySnackbar.show();

                }
            })
            .create()
            .show();
}


private void result(){
          //your code
}

    @RequiresApi(api = Build.VERSION_CODES.M)
public class NeverAskAgain implements View.OnClickListener{
    @Override
    public void onClick(View view)
    {
        goToSettings();
    }
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void goToSettings() {
    Intent myAppSettings = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + getPackageName()));
    finish();
    myAppSettings.addCategory(Intent.CATEGORY_DEFAULT);
    myAppSettings.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivityForResult(myAppSettings, REQUEST_APP_SETTINGS);
}
public class cancelButton implements View.OnClickListener{
    @Override
    public void onClick(View view){
        Toast.makeText(MainActivity.this,"To use this app , you must grant storage permission",Toast.LENGTH_SHORT);
        finish();
    }
    }


 @Override
@RequiresApi(api = Build.VERSION_CODES.M)
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode,permissions,grantResults);

    switch(requestCode) {
        case REQUEST_ACCESS:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // permission is granted
                    result();
                    break;
                }
                else if (!shouldShowRequestPermissionRationale(permissions[0])){
                    showMessageOKCancel("You choose Never Ask Again,option",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Snackbar mySnackbar = Snackbar.make(findViewById(R.id.coordinatorlayout), "Permission=>Storage=>On", Snackbar.LENGTH_INDEFINITE);
                        mySnackbar.setAction("Settings", new NeverAskAgain());
                        mySnackbar.show();
                    }
                     });
                    break;
                }
                else {
                    showMessageOKCancel("You Denid permission Request..",
                    new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            requestPermissions(new String[]{storagePermissions}, REQUEST_ACCESS);
                        }
                    });
                    break;
                }
        }
}
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Abhishek Garg
  • 3,092
  • 26
  • 30