372

According to this: http://developer.android.com/preview/features/runtime-permissions.html#coding an app can check for runtime permissions and request permissions if it hasn't been granted already. The following dialog will be displayed then:

enter image description here

In case the user declines an important permission, imo an app should display an explanation why the permission is needed and what impact declining has. That dialog has two options:

  1. re-try again (permission is requested again)
  2. deny (app will work without that permission).

If the user checks Never ask again however, the second dialog with the explanation shouldn't be shown, especially if the user already declined once before. Now the question is: how does my app know whether the user has checked the Never ask again? IMO the onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) doesn't give me that information.

A second question would be: does Google have plans to incorporate a custom message in the permission dialog that would explain why the app needs the permission? That way there would never be a second dialog which would certainly make for a better ux.

JJD
  • 50,076
  • 60
  • 203
  • 339
Emanuel Moecklin
  • 28,488
  • 11
  • 69
  • 85
  • 14
    "does Google have plans to incorporate a custom message in the permission dialog that would explain why the app needs the permission?" -- in the Google I|O presentation about the M permission system, I seem to recall somebody asked in the Q&A, and the answer was that they're thinking about it. – CommonsWare Jun 08 '15 at 21:19
  • 1
    Didn't test it myself, but documentation say about Activity.shouldShowRequestPermissionRationale(String) : This method returns true if the app has requested this permission previously and the user denied the request. That indicates that you should probably explain to the user why you need the permission. If the user turned down the permission request in the past and chose the Don't ask again option in the permission request system dialog, this method returns false. The method also returns false if the device policy prohibits the app from having that permission. – Fraid Jul 17 '15 at 13:45
  • 1
    @Fraid: looks like they added this with preview #2 of Android M: http://developer.android.com/preview/support.html#preview2-notes and it's probably what I was looking for. I can't test it right now but will do so next week. If it does what I hope it does, you can post it as an answer and get some reputation. In the meantime this might help others: https://www.youtube.com/watch?v=f17qe9vZ8RM – Emanuel Moecklin Jul 21 '15 at 16:16
  • example of Dangerous Permissions and Special Permissions: https://github.com/henrychuangtw/AndroidRuntimePermission – HenryChuang May 25 '16 at 06:52
  • I don't like this whole permission thingy. Made it more confusing – Alex Jul 22 '16 at 20:10
  • 1
    @Alex harder for developers that's for sure but from a user perspective being able to grant or deny specific permissions makes sense. The main problem I see is that the granularity of permissions is very inconsistent and you end up asking for a permission that might have almost nothing to do with what you're trying to do in your app (e.g. contacts permission when I want to connect to Google Drive because that needs a list of the device accounts for authentication purposes and the account permission is part of the contact permission group). – Emanuel Moecklin Jul 22 '16 at 20:14
  • Check this https://stackoverflow.com/a/55116122/6667442 – Ketan Ramani Mar 12 '19 at 07:32

29 Answers29

393

Developer Preview 2 brings some changes to how permissions are requested by the app (see also http://developer.android.com/preview/support.html#preview2-notes).

The first dialog now looks like this:

enter image description here

There's no "Never show again" check-box (unlike developer preview 1). If the user denies the permission and if the permission is essential for the app it could present another dialog to explain the reason the app asks for that permission, e.g. like this:

enter image description here

If the user declines again the app should either shut down if it absolutely needs that permission or keep running with limited functionality. If the user reconsiders (and selects re-try), the permission is requested again. This time the prompt looks like this:

enter image description here

The second time the "Never ask again" check-box is shown. If the user denies again and the check-box is ticked nothing more should happen. Whether or not the check-box is ticked can be determined by using Activity.shouldShowRequestPermissionRationale(String), e.g. like this:

if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) {...

That's what the Android documentation says (https://developer.android.com/training/permissions/requesting.html):

To help find the situations where you need to provide extra explanation, the system provides the Activity.shouldShowRequestPermissionRationale(String) method. This method returns true if the app has requested this permission previously and the user denied the request. That indicates that you should probably explain to the user why you need the permission.

If the user turned down the permission request in the past and chose the Don't ask again option in the permission request system dialog, this method returns false. The method also returns false if the device policy prohibits the app from having that permission.

To know if the user denied with "never ask again" you can check again the shouldShowRequestPermissionRationale method in your onRequestPermissionsResult when the user did not grant the permission.

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == REQUEST_PERMISSION) {
        // for each permission check if the user granted/denied them
        // you may want to group the rationale in a single dialog,
        // this is just an example
        for (int i = 0, len = permissions.length; i < len; i++) {
            String permission = permissions[i];
            if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
            // user rejected the permission
                boolean showRationale = shouldShowRequestPermissionRationale( permission );
                if (! showRationale) {
                    // user also CHECKED "never ask again"
                    // you can either enable some fall back,
                    // disable features of your app
                    // or open another dialog explaining
                    // again the permission and directing to
                    // the app setting
                } else if (Manifest.permission.WRITE_CONTACTS.equals(permission)) {
                    showRationale(permission, R.string.permission_denied_contacts);
                    // user did NOT check "never ask again"
                    // this is a good place to explain the user
                    // why you need the permission and ask if he wants
                    // to accept it (the rationale)
                } else if ( /* possibly check more permissions...*/ ) {
                }
            }
        }
    }
}

You can open your app setting with this code:

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, REQUEST_PERMISSION_SETTING);

There is no way of sending the user directly to the Authorization page.

Emanuel Moecklin
  • 28,488
  • 11
  • 69
  • 85
  • 44
    I verified return value of shouldShowRequestPermissionRationale() method to false for checking if user selected "Never ask again". But I am also getting its value as false for very first time when ask for permission. So I am not able to differentiate if user selected "Never ask again" checkbox or not. Please suggest?? – Sagar Trehan Oct 05 '15 at 08:24
  • Works fine for me. Maybe this is the reason: "The method also returns false if the device policy prohibits the app from having that permission"? – Emanuel Moecklin Oct 05 '15 at 16:28
  • 41
    According to my understanding shouldShowRationalePermissionRationale() method returns false in three cases: 1. If we call this method very first time before asking permission. 2. If user selects "Don't ask again" and deny permission. 3. If the device policy prohibits the app from having that permission – Sagar Trehan Oct 07 '15 at 09:01
  • 1
    That's correct and that's how it should work imo. You're certainly not going to show a rationale on why the user needs a permission before you ask for that permission. The question really isn't if the "Don't ask again" was selected but if a rationale should be shown. The question about the "Don't ask again" only gets relevant after the permission was requested for the first time. – Emanuel Moecklin Oct 07 '15 at 13:27
  • 49
    All good... but we, developers, really need to know if the user said "never ask again" or not. I have a nice button to access a feature. The first time the user click: should ask rationale? nope, ask permission. The user deny. User click again the button: rationale? yep! Show rationale, user say Ok, then deny and never ask again (ok he is an idiot, but users often are). Later user press again the button, rationale? nope, ask permission, nothing happens for the user. I really need a way, there, to tell the user: hey man if you want this feature now go to the app setting and give the permission. – Daniele Segato Oct 16 '15 at 14:19
  • let me add a little bit to this: libraries. They can ask permissions too and you have no control on what they do. – Daniele Segato Oct 16 '15 at 14:20
  • I think I found a way and edited your response adding it – Daniele Segato Oct 16 '15 at 15:07
  • @DanieleSegato thanks for adding the example. That's exactly what I'm doing in my app. I modified your code to cover the general case if the app requests more than one permission at a time. – Emanuel Moecklin Oct 16 '15 at 16:59
  • 5
    Great @EmanuelMoecklin this is better then Google Documentation now :D – Daniele Segato Oct 17 '15 at 14:58
  • wait, your modification is actually a bit missleading :) I edit it again a little – Daniele Segato Oct 17 '15 at 15:00
  • 1
    If you think my explanation is misleading then you missed the point. I had to revert some of your changes because you were oversimplifying it. If you check for multiple permissions you want to present different explanations for different permissions. – Emanuel Moecklin Oct 19 '15 at 01:58
  • How come the else-if block does say that the the user has denied flagging 'Never Ask Again'. Can you please check again and clarify the case, because I think it's wrong and misleading. @EmanuelMoecklin – Can Elmas Oct 19 '15 at 12:14
  • @Can Elmas: thanks for pointing this out, I modified the if else statement, should be correct now – Emanuel Moecklin Oct 19 '15 at 12:26
  • @EmanuelMoecklin I don't think the current API capabilities let us distinguish between the case where showRationale is false and the user denies the request for the first time and the one user denies with flagging 'Never ask again'. So for both of these cases, your modified code will execute the first if statement. – Can Elmas Oct 19 '15 at 12:47
  • 4
    onRequestPermissionsResult won't be called unless you request the permission. Since there's no check-box "Never ask again" the first time the permission is requested, shouldShowRequestPermissionRationale will return True (permission requested but without never ask again). Consequently the rationale is always shown the first time the user rejects the permission but after that only if the check-box wasn't ticked. – Emanuel Moecklin Oct 19 '15 at 15:33
  • 1
    @CanElmas I agree that these two cases can't be distinguished using the API although you can keep track of that yourself using some SharedPreference variable that you set in onRequestPermissionsResult. This doesn't matter though if you use the request first - then show rationale pattern. It's different when you show the rationale first and then request the permission (which is what Google recommends actually) – Emanuel Moecklin Oct 22 '15 at 17:57
  • @EmanuelMoecklin I tried to come up with an easy-to-use API that handles this whole new permission model. I'll appreciate your feedback. https://github.com/canelmas/let – Can Elmas Nov 23 '15 at 10:04
  • 1
    When you ask for a permission couldn't you just save a SharedPref `boolean` as `true`. Then when you end up calling `shouldShowRequestPermissionRationale()` you know if it is the first time or not. – Rockin4Life33 May 03 '16 at 21:16
  • @Anovative It doesn't matter whether the app requests the permission the first time or the nth time, all you want to know is if you can bother the user again with a rationale. If the user checked the box "Never ask again", you don't want to show the rationale. – Emanuel Moecklin May 04 '16 at 03:58
  • @EmanuelMoecklin Sure, I agree that the user should not be bothered if they say they don't want to grant the permission and check "Never ask again" but if your app MUST have that permission to function correctly they should be aware that by not granting that permission they just as well uninstall the app as it will not work as intended. Imagine some Contact Management App, what if the user says NO; you cannot access my contacts and don't ask again... What should be done? The user is just might not be smart enough to understand why the app doesn't work they way they expect. – Rockin4Life33 May 04 '16 at 23:17
  • Furthermore, I give a quick link to the App Settings if they have denied permission to a feature breaking permission and check "Never ask again" so that they can read some reasoning and click the link for ease of access to get to the App Settings -> Permissions to grant permission if they decide they actually do want to be able to use the app. ...in the end you can only do so much... IMO! – Rockin4Life33 May 04 '16 at 23:20
  • @Anovative I agree and I do the exact same thing. Here's my simplified process: https://www.lucidchart.com/documents/view/8ef02146-8b4a-41c0-b06f-f8e8df9d2587. The decision in red is the one that (among other things) needs to take the "Never ask again" option into consideration. – Emanuel Moecklin May 05 '16 at 14:26
  • @EmanuelMoecklin Great flow chart. My first comment must not have came across correctly; your flow chart outlines exactly how I'm handling it. ...we're on the same page! – Rockin4Life33 May 05 '16 at 14:52
  • 1
    @EmanuelMoecklin "You're certainly not going to show a rationale on why the user needs a permission before you ask for that permission." I disagree. It is possible that a permission request may not be clear to a user and warrant showing a "first time" rationale before the permission is requested. Example: An app wants to read the user's custom ringtones from the sdcard at startup, but the system text "Allow X to access photos, media, and files on your device?", while true, is almost uselessly too vague and generic without a context; A custom "…to read your ringtones…" message would be nicer. – swooby Jul 06 '16 at 20:48
  • 1
    @EmanuelMoecklin "Since there's no check-box "Never ask again" the first time the permission is requested, shouldShowRequestPermissionRationale will return True (permission requested but without never ask again)". I disagree. https://developer.android.com/training/permissions/requesting.html says "This method returns true if the app has requested this permission previously *and* the user denied the request." If the permission has not been previously requested then this will return false. There is no reliable official Android API to differentiate between "Never asked" and "Don't ask again". :( – swooby Jul 06 '16 at 20:55
  • @swooby I agree that a rationale can (and according to Google should be shown) before the permission is requested. IMO it depends on how obvious requesting a permission is. A camera app requesting access to the camera should probably not show the rationale beforehand but for your ringtone example, showing the rationale beforehand is probably best. – Emanuel Moecklin Jul 06 '16 at 21:45
  • @swooby The statement "Since there's no check-box "Never ask again" the first time the permission is requested, shouldShowRequestPermissionRationale will return True (permission requested but without never ask again)" is correct because this is after the app requested the permission (in the onRequestPermissionsResult). I agree that the method returns false before you request the permission the first time but that's not what my statement says. You're taking it out of context by ignoring the first part that says "onRequestPermissionsResult won't be called unless you request the permission". – Emanuel Moecklin Jul 06 '16 at 22:11
  • 1
    @EmanuelMoecklin For my app's launch logic I want to first use ContextCompat.checkSelfPermission(...) to check for permissions, and if a permission isn't granted then I want to provide the user with a rationale *before* I make the actual call to ActivityCompat.requestPermissions(...). Unfortunately, ActivityCompat.shouldShowRequestPermissionRationale(...) only appears to be useful after requestPermissons has been called. Google really messed up implementing Permissions handling in M. The old way has legit use cases and they should have at least let apps opt out of the new way. – swooby Jul 07 '16 at 01:56
  • 1
    @swooby you can use a SharedPreference flag/boolean to keep track whether the app has already shown the rationale or not. Something along the lines: if(ContextCompat.checkSelfPermission(activity, androidPermissionName) != PackageManager.PERMISSION_GRANTED && (! SharedPreference.getBoolean("RationaleShown", false) || ActivityCompat.shouldShowRequestPermissionRationale()) { showRationale(); }. In showRationale you would set the SharedPreference flag to true. – Emanuel Moecklin Jul 07 '16 at 22:19
  • @EmanuelMoecklin Understood, but I agree w/ CommonsWare in a similar topic that that solution isn't very good because it can get out of sync. – swooby Jul 07 '16 at 23:29
  • as per many docs ,if user decline permission then it returns `grantResults` empty in above code we have checked `else if (grantResults[0] == PackageManager.PERMISSION_DENIED)` so in `else if` part it throw Arrayindexoutofbound exception,i have tested code when user decline permission `grantResults` is not emplty for my case,but i have seen crash report on fabric console for `grantResults` there is many crash with `arrayindexoutofbound`,you have any explanation for this,why some device return `grantResults` empty and some device `grantResults` have value with decline when user decline. – Om Infowave Developers Jun 09 '18 at 04:22
  • call shouldShowRequestPermissionRationale() inside --> on RequestPermissionsResult(). – Soon Santos Jun 28 '18 at 23:43
  • Please note: If the user presses back button while the permission dialog is displayed for the first time, then also this condition will satisfy if (grantResults[i] == PackageManager.PERMISSION_DENIED) { if (! showRationale) { } } – Rupam Das Feb 23 '21 at 17:14
112

You can check shouldShowRequestPermissionRationale() in your onRequestPermissionsResult().

shouldShowRequestPermissionRationale https://youtu.be/C8lUdPVSzDk?t=2m23s

Check whether permission was granted or not in onRequestPermissionsResult(). If not then check shouldShowRequestPermissionRationale().

  1. If this method returns true then show an explanation that why this particular permission is needed. Then depending on user's choice again requestPermissions().
  2. If it returns false then show an error message that permission was not granted and app cannot proceed further or a particular feature is disabled.

Below is sample code.

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case STORAGE_PERMISSION_REQUEST:
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // permission was granted :)
                downloadFile();
            } else {
                // permission was not granted
                if (getActivity() == null) {
                    return;
                }
                if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    showStoragePermissionRationale();
                } else {
                    Snackbar snackbar = Snackbar.make(getView(), getResources().getString(R.string.message_no_storage_permission_snackbar), Snackbar.LENGTH_LONG);
                    snackbar.setAction(getResources().getString(R.string.settings), new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (getActivity() == null) {
                                return;
                            }
                            Intent intent = new Intent();
                            intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                            Uri uri = Uri.fromParts("package", getActivity().getPackageName(), null);
                            intent.setData(uri);
                            OrderDetailFragment.this.startActivity(intent);
                        }
                    });
                    snackbar.show();
                }
            }
            break;
    }
}

Apparently, google maps does exactly this for location permission.

Abhinav Chauhan
  • 1,900
  • 1
  • 18
  • 18
  • Thank you for the picture and the Youtube link. It matches more or less my own answer. It has to be noted that the question was asked when only the developer preview 1 was available which didn't have the shouldShowRequestPermissionRationale method. – Emanuel Moecklin Nov 13 '15 at 17:22
  • i'm new in android and i want to over ride this onRequestPermissionsResult() method. but I'm getting error that it must implement a super type method. can you tell how to use this – Andrain Nov 18 '15 at 08:50
46

Here is a nice and easy method to check the current permission status:

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

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

    @PermissionStatus 
    public static int getPermissionStatus(Activity activity, String androidPermissionName) {
        if(ContextCompat.checkSelfPermission(activity, androidPermissionName) != PackageManager.PERMISSION_GRANTED) {
            if(!ActivityCompat.shouldShowRequestPermissionRationale(activity, androidPermissionName)){
                return BLOCKED_OR_NEVER_ASKED;
            }
            return DENIED;
        }
        return GRANTED;
    }

Caveat: returns BLOCKED_OR_NEVER_ASKED the first app start, before the user accepted/denied the permission through the user prompt (on sdk 23+ devices)

Update:

The Android support library now also seems to have a very similar class android.support.v4.content.PermissionChecker which contains a checkSelfPermission() which returns:

public static final int PERMISSION_GRANTED = 0;
public static final int PERMISSION_DENIED = -1;
public static final int PERMISSION_DENIED_APP_OP = -2;
Patrick
  • 33,984
  • 10
  • 106
  • 126
43

Once the user has marked "Do not ask again," the question can not be displayed again. But it can be explained to the user that he has previously denied the permission and must grant permission in the settings. And reference him to the settings, with the following code:

@Override
public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults) {

    if (grantResults.length > 0
            && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        // now, you have permission go ahead
        // TODO: something

    } else {

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                Manifest.permission.READ_CALL_LOG)) {
            // now, user has denied permission (but not permanently!)

        } else {

            // now, user has denied permission permanently!

            Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), "You have previously declined this permission.\n" +
                "You must approve this permission in \"Permissions\" in the app settings on your device.", Snackbar.LENGTH_LONG).setAction("Settings", new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + BuildConfig.APPLICATION_ID)));

            }
        });
        View snackbarView = snackbar.getView();
        TextView textView = (TextView) snackbarView.findViewById(android.support.design.R.id.snackbar_text);
        textView.setMaxLines(5);  //Or as much as you need
        snackbar.show();

        }

    }
    return;
}
  • 2
    in migration to androidX you can replace android.support.design.R with com.google.android.material.R – Ridha Rezzag Jul 07 '19 at 10:10
  • This does not work when asking for COARSE and FINE location permission, which you have to ask for both in Android SDK 31. In my case if the user picks COARSE location, if I check shouldShowRequestPermissionRationale for PRECISE location it goes in the the else for 'user has denied permission permanently', which is not true. – lostintranslation Sep 28 '22 at 22:01
30

You can determine it by checking if permission rationale is to be shown inside the onRequestPermissionsResult() callback method. And if you find any permission set to never ask again, you can request users to grant permissions from the settings.

My full implementation would be like below. It works for both single or multiple permissions requests. Use the following or directly use my library.

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if(permissions.length == 0){
        return;
    }
    boolean allPermissionsGranted = true;
    if(grantResults.length>0){
        for(int grantResult: grantResults){
            if(grantResult != PackageManager.PERMISSION_GRANTED){
                allPermissionsGranted = false;
                break;
            }
        }
    }
    if(!allPermissionsGranted){
        boolean somePermissionsForeverDenied = false;
        for(String permission: permissions){
            if(ActivityCompat.shouldShowRequestPermissionRationale(this, permission)){
                //denied
                Log.e("denied", permission);
            }else{
                if(ActivityCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED){
                    //allowed
                    Log.e("allowed", permission);
                } else{
                    //set to never ask again
                    Log.e("set to never ask again", permission);
                    somePermissionsForeverDenied = true;
                }
            }
        }
        if(somePermissionsForeverDenied){
            final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
            alertDialogBuilder.setTitle("Permissions Required")
                    .setMessage("You have forcefully denied some of the required permissions " +
                            "for this action. Please open settings, go to permissions and allow them.")
                    .setPositiveButton("Settings", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                                    Uri.fromParts("package", getPackageName(), null));
                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            startActivity(intent);
                        }
                    })
                    .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                        }
                    })
                    .setCancelable(false)
                    .create()
                    .show();
        }
    } else {
        switch (requestCode) {
            //act according to the request code used while requesting the permission(s).
        }
    }
}
Nabin Bhandari
  • 15,949
  • 6
  • 45
  • 59
  • hii @nabin my requirement is when i click on download button(which download pdf file) that time have to check write permission is allow or denied so how to use this code! can you guide me plz – Rucha Bhatt Joshi Jun 16 '17 at 06:38
  • hello @RuchaBhatt Have a look at my library. https://github.com/nabinbhandari/Android-Permissions – Nabin Bhandari Jun 20 '17 at 17:12
26

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 of detailed working example

Community
  • 1
  • 1
Nicks
  • 16,030
  • 8
  • 58
  • 65
  • 3
    This is the correct way to detect if user has selected never ask again option. – Muhammad Babar May 24 '17 at 10:31
  • Ah, the key here is that you handle this in the `onRequestPermissionsResult`, not when actually requesting the permission. – Joshua Pinter May 12 '18 at 15:27
  • Return true:-- Any time user clicks Deny permissions (including the very first time). This is not true when asking for both fine and coarse location. If the user picks coarse, if you ask shouldShowRequestPermissionRationale for fine location, the first time it returns false, after that it will return true. – lostintranslation Sep 29 '22 at 16:18
21

If you want to detect all the "states" (first time denied, just been denied, just been denied with "Never Ask Again" or permanently denied) you can do the following:

Create 2 Booleans:

private boolean beforeClickPermissionRat;
private boolean afterClickPermissionRat;

Set the first one before asking for permission:

beforeClickPermissionRat = shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE);

Set the second one inside your onRequestPermissionsResult method:

afterClickPermissionRat = shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE);

Use the following "truth table" to do whatever you need in onRequestPermissionsResult() (after checking that you still don't have the permission):

// before after
// FALSE  FALSE  =  Was denied permanently, still denied permanently --> App Settings
// FALSE  TRUE   =  First time deny, not denied permanently yet --> Nothing
// TRUE   FALSE  =  Just been permanently denied --> Changing my caption to "Go to app settings to edit permissions"
// TRUE   TRUE   =  Wasn't denied permanently, still not denied permanently --> Nothing
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
mVck
  • 2,910
  • 2
  • 18
  • 20
  • 1
    There's no point in checking the shouldShowRequestPermissionRationale before calling requestPermissions unless you want to show the rationale before requesting the permission. Showing the rationale only after the user denied the permission seems to be how most apps handle it nowadays though. – Emanuel Moecklin Dec 23 '16 at 21:59
  • 3
    @EmanuelMoecklin, as far as I know it's the only way to check if it's already been denied (by checking it before and after, as explained in my truth table) or if it's a first time deny (in my case I redirect the user to the app settings if it's permanently denied) – mVck Jan 30 '17 at 18:03
  • 1
    `// TRUE FALSE` also occurs when user allows a permission after previously denying it. – samus Jul 25 '17 at 17:48
  • @mVck that means, when `afterClickPermissionRat` is false, it is permanently denied. – ARiF Nov 25 '20 at 16:07
  • 1
    This solution is based on undocumented behaviour of `shouldShowRequestPermissionRationale()` and may (s/may/will) be broken in future versions of Android. – rds Feb 23 '21 at 16:43
  • ```if(!beforeClickPermissionRat && !afterClickPermissionRat){ openSettings() }```, So As far as I understand, this condition should be applied in ```onRequestPermissionResult```, shouldn't it? – Azamat Mahkamov May 04 '21 at 05:16
  • 1
    in my case I am using a dedicated screen to handle my permissions (like the one used in waze) so the rational is kind of always there . so this answer is a perfect fit . the answer is simple yet elegant , all other solutions needed to store some data and do a combination of checks but they fail in cases such as allow once .... . Thanks @mVck your name will be mentioned along side your truth table in my source code documentation :). – A.Alqadomi Oct 05 '22 at 07:52
  • One note though . the case of FALSE FALSE it will fail to detect correctly if the user simply dismiss the permission dialog using the back button . if requested for the first time this will cause the shouldShowRequestPermissionRationale to be false after the request finish . to fix this I have considered the time needed between the request and the final response . usually it takes less than a 100 milli seconds to report as fail if it was permanently disabled, so I coved all the cases using a combination of both methods . – A.Alqadomi Oct 06 '22 at 12:38
11

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();
                        }
                    });

how does my app know whether the user has checked the "Never ask again"?

If user checked Never ask again, you'll get callback on onPermissionDisabled.

Happy coding :)

muthuraj
  • 1,033
  • 15
  • 22
  • shouldShowRequestPermissionRationale i got error here, can you plz help me. – Rucha Bhatt Joshi Jun 16 '17 at 07:17
  • i cant find this method shouldShowRequestPermissionRationale may be i failed to get context.. but it's fine i found other alternate solution.. Thank you for help :) – Rucha Bhatt Joshi Jun 16 '17 at 08:50
  • 1
    My bad. shouldShowRequestPermissionRationale is available through Activity, not context. I updated my answer by casting the context to Activity before calling that method. Check it out :) – muthuraj Jun 16 '17 at 13:52
  • 1
    This is the only way to get around the first false value returned by `shouldShowRequestPermissionRationale`, saving to preference the request sent to the user. I had the same idea and found your answer. Nice job man – MatPag Dec 18 '19 at 11:31
  • This is a hack and breaks if the user ever disables a permission and then tries to access the feature again – Nick Cardoso Jan 20 '21 at 11:55
  • @NickCardoso Can you explain how this is a hack and how this will break? When user disables a permission from permission settings, the app's process will be killed and restarted. And when we call `checkPermission` method here, `onPermissionDisabled` callback will be called. – muthuraj Jan 21 '21 at 11:23
  • 1
    That should be trivial to understand - if the user turns off the permission in system, it wont update the workaround preference you made. Then when the user tries to use the feature again, instead of getting the expected system dialog they skip the expected first step and immediately trigger what you called onPermissionDisabled - also now on android 11, even the OS could externally revoke permission – Nick Cardoso Jan 22 '21 at 08:51
  • Ah, I get it now. It also explains why onPermissionDisablled flow is invoked when I give `Only once` option in Android 11 and opening the app next time. I'll try to reproduce and fix this and update the answer here. Thanks for the feedback. – muthuraj Jan 28 '21 at 08:57
6

The method shouldShowRequestPermissionRationale() can be used to check whether the user selected the 'never asked again' option and denied the permission. There's plenty of code examples, so I would rather explain how to use it for such a purpose, because I think its name and its implementation makes this more complicated that it actually is.

As explained in Requesting Permissions at Run Time, that method returns true if the option 'never ask again' is visible, false otherwise; so it returns false the very first time a dialog is shown, then from the second time on it returns true, and only if the user deny the permission selecting the option, at that point it returns false again.

To detect such a case, either you can detect the sequence false-true-false, or (more simple) you can have a flag which keeps track of the initial time the dialog is shown. After that, that method returns either true or false, where the false will allow you to detect when the option is selected.

Alessio
  • 3,063
  • 1
  • 15
  • 17
5

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

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

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

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

https://stackoverflow.com/a/58114769/5151336

vishnu benny
  • 998
  • 1
  • 11
  • 15
  • 2
    This is not entirely true. `shouldShowRequestPermissionRationale` will return false, if the user performs an outer click or backpress on the system dialog. Even though the user has not perma denied the permission. – JacksOnF1re Aug 16 '21 at 14:05
  • @JacksOnF1re This is the flow to be considered as a user allowed permission then execute the action. The checks also should be adjusted with cases of implementation. – vishnu benny Aug 18 '21 at 12:16
4

You can use

shouldShowRequestPermissionRationale()

inside

onRequestPermissionsResult()

See the example below:

Check if it has permission when the user clicks the button:

@Override
public void onClick(View v) {
    if (v.getId() == R.id.appCompatBtn_changeProfileCoverPhoto) {
        if (Build.VERSION.SDK_INT < 23) { // API < 23 don't need to ask permission
            navigateTo(MainActivity.class); // Navigate to activity to change photos
        } else {
            if (ContextCompat.checkSelfPermission(SettingsActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED) {
                // Permission is not granted yet. Ask for permission...
                requestWriteExternalPermission();
            } else {
                // Permission is already granted, good to go :)
                navigateTo(MainActivity.class);
            }
        } 
    }
}

When the user answer the permission dialog box we will go to onRequestPermissionResult:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    if (requestCode == WRITE_EXTERNAL_PERMISSION_REQUEST_CODE) {
        // Case 1. Permission is granted.  
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {  
            if (ContextCompat.checkSelfPermission(SettingsActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    == PackageManager.PERMISSION_GRANTED) {
                // Before navigating, I still check one more time the permission for good practice.
                navigateTo(MainActivity.class);
            }
        } else { // Case 2. Permission was refused
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                // Case 2.1. shouldShowRequest... returns true because the
                // permission was denied before. If it is the first time the app is running we will 
                // end up in this part of the code. Because he need to deny at least once to get 
                // to onRequestPermissionsResult. 
                Snackbar snackbar = Snackbar.make(findViewById(R.id.relLayout_container), R.string.you_must_verify_permissions_to_send_media, Snackbar.LENGTH_LONG);
                snackbar.setAction("VERIFY", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        ActivityCompat.requestPermissions(SettingsActivity.this
                                , new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}
                                , WRITE_EXTERNAL_PERMISSION_REQUEST_CODE);
                    }
                });
                snackbar.show();
            } else {
                // Case 2.2. Permission was already denied and the user checked "Never ask again". 
                // Navigate user to settings if he choose to allow this time.
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setMessage(R.string.instructions_to_turn_on_storage_permission)
                        .setPositiveButton(getString(R.string.settings), new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                Intent settingsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                Uri uri = Uri.fromParts("package", getPackageName(), null);
                                settingsIntent.setData(uri);
                                startActivityForResult(settingsIntent, 7);
                            }
                        })
                        .setNegativeButton(getString(R.string.not_now), null);
                Dialog dialog = builder.create();
                dialog.show();
            }
        }
    }

}
Soon Santos
  • 2,107
  • 22
  • 43
4

OnRequestPermissionResult-free and shouldShowRequestPermissionRationale-free method:

public static void requestDangerousPermission(AppCompatActivity activity, String permission) {
        if (hasPermission(activity, permission)) return;
        requestPermission();

        new Handler().postDelayed(() -> {
            if (activity.getLifecycle().getCurrentState() == Lifecycle.State.RESUMED) {
                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                intent.setData(Uri.parse("package:" + context.getPackageName()));
                context.startActivity(intent);
            }
        }, 250);
    }

Opens device settings after 250ms if no permission popup happened (which is the case if 'Never ask again' was selected.

Martin
  • 97
  • 1
  • 6
  • How are you sure that the user is able to react to the permission dialog within 250ms? – Edric Nov 10 '20 at 07:18
  • That's not necessarily. If the user were to react within 250ms (which is quite impossible) the device settings would open. But the permission dialog is dismissed by the system within 250ms if Never ask again was selected previously, that's why this works. – Martin Nov 11 '20 at 08:08
  • Awesome, have tested this implementation and it works in all the cases atleast in my app. – Bawender Yandra May 02 '23 at 08:15
3

Complete explanation for every case of permission

/**
 *    Case 1: User doesn't have permission
 *    Case 2: User has permission
 *
 *    Case 3: User has never seen the permission Dialog
 *    Case 4: User has denied permission once but he din't clicked on "Never Show again" check box
 *    Case 5: User denied the permission and also clicked on the "Never Show again" check box.
 *    Case 6: User has allowed the permission
 *
 */
public void handlePermission() {
    if (ContextCompat.checkSelfPermission(MainActivity.this,
            Manifest.permission.WRITE_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED) {
        // This is Case 1. Now we need to check further if permission was shown before or not

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)) {

            // This is Case 4.
        } else {
            // This is Case 3. Request for permission here
        }

    } else {
        // This is Case 2. You have permission now you can do anything related to it
    }
}

public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        // This is Case 2 (Permission is now granted)
    } else {
        // This is Case 1 again as Permission is not granted by user

        //Now further we check if used denied permanently or not
        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
            // case 4 User has denied permission but not permanently

        } else {
            // case 5. Permission denied permanently.
            // You can open Permission setting's page from here now.
        }

    }
}
saksham
  • 3,173
  • 1
  • 21
  • 23
3

A useful function to determine if an arbitrary permission has been blocked from requesting (in Kotlin):

private fun isPermissionBlockedFromAsking(activity: Activity, permission: String): Boolean {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        return ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED
            && !activity.shouldShowRequestPermissionRationale(permission)
            && PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(permission, false)
    }
    return false
}

Use of this requires setting a shared preference boolean with the name of your desired permission (e.g. android.Manifest.permission.READ_PHONE_STATE) to true when you first request a permission.


Explanation:

Build.VERSION.SDK_INT >= Build.VERSION_CODES.M as some of the code may only be run on API level 23+.

ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED to check we don't already have the permission.

!activity.shouldShowRequestPermissionRationale(permission) to check whether the user has denied the app asking again. Due to quirks of this function, the following line is also required.

PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(permission, false) this is used (along with setting the value to true on first permission request) to distinguish between the "Never asked" and "Never ask again" states, as the previous line doesn't return this information.

Jake Lee
  • 7,549
  • 8
  • 45
  • 86
3

The way I figured this out was a bit new to me. I have to keep a reference if the user has ever chosen one decision. In this way if the permission is not granted, I can tell the user is there for the first time and should be prompted to see the permission pop up, or the user has temporarily or permanently denied it.

psudocode:

if( granted ) {
 // you are set
} else if( requiresRationale() ) {
 // in the ui let the user know he has to tap and launch permission
 button.onSetClickListener { requestPermission() }
} else if( sharedPreferences.getBoolean("permission", false) ) {
  // so user has already decided to deny permission, then it is permanent
  launchAppSettings()
} else {
  // user's first encounter, request permission
  requestPermission()
}

demo is attached as a gif in the readme file. https://github.com/juanmendez/android-sdk-updates/tree/api/android-permissions/single

Juan Mendez
  • 2,658
  • 1
  • 27
  • 23
2

Please don't throw stones at me for this solution.

This works but is a bit "hacky".

When you call requestPermissions, register the current time.

        mAskedPermissionTime = System.currentTimeMillis();

Then in onRequestPermissionsResult

if the result is not granted, check the time again.

 if (System.currentTimeMillis() - mAskedPermissionTime < 100)

Since the user did cannot possibly click so fast on the deny button, we know that he selected "never ask again" because the callback is instant.

Use at your own risks.

Antzi
  • 12,831
  • 7
  • 48
  • 74
  • 1
    what if we see that requested dialog for 5 minutes and then deny? – saksham May 11 '18 at 09:59
  • Then what is the use of this if it can't fulfill the basic requirement. A code can be a hack as accepted if it clearly fulfills all the requirements in every case else not. – saksham May 13 '18 at 05:42
  • 1
    Yeah this is bad. Automatic testers like this could maybe manage to click faster than that: https://developer.android.com/training/testing/crawler – stackzebra May 10 '20 at 14:22
1

I wrote a shorthand for permission request in Android M. This code also handles backwards compatibility to older Android versions.

All the ugly code is extracted into a Fragment which attaches and detaches itself to the Activity requesting the permissions.You can use PermissionRequestManager as following:

new PermissionRequestManager()
        // We need a AppCompatActivity here, if you are not using support libraries you will have to slightly change 
        // the PermissionReuqestManager class
        .withActivity(this)

        // List all permissions you need
        .withPermissions(android.Manifest.permission.CALL_PHONE, android.Manifest.permission.READ_CALENDAR)

        // This Runnable is called whenever the request was successfull
        .withSuccessHandler(new Runnable() {
            @Override
            public void run() {
                // Do something with your permissions!
                // This is called after the user has granted all 
                // permissions, we are one a older platform where 
                // the user does not need to grant permissions 
                // manually, or all permissions are already granted

            }
        })

        // Optional, called when the user did not grant all permissions
        .withFailureHandler(new Runnable() {
            @Override
            public void run() {
                // This is called if the user has rejected one or all of the requested permissions
                L.e(this.getClass().getSimpleName(), "Unable to request permission");

            }
        })

        // After calling this, the user is prompted to grant the rights
        .request();

Take a look: https://gist.github.com/crysxd/385b57d74045a8bd67c4110c34ab74aa

crysxd
  • 3,177
  • 20
  • 32
1

Instead you will receive callback on onRequestPermissionsResult() as PERMISSION_DENIED when you request permission again while falling in false condition of shouldShowRequestPermissionRationale()

From Android doc:

When the system asks the user to grant a permission, the user has the option of telling the system not to ask for that permission again. In that case, any time an app uses requestPermissions() to ask for that permission again, the system immediately denies the request. The system calls your onRequestPermissionsResult() callback method and passes PERMISSION_DENIED, the same way it would if the user had explicitly rejected your request again. This means that when you call requestPermissions(), you cannot assume that any direct interaction with the user has taken place.

Muhammad Farhan Habib
  • 1,859
  • 20
  • 23
1
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
    switch (requestCode) {
        case PERMISSIONS_REQUEST_EXTERNAL_STORAGE: {
            if (grantResults.length > 0) {
                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    // Denied
                } else {
                    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                        // To what you want
                    } else {
                       // Bob never checked click
                    }
                }
            }
        }
    }
}
Axe
  • 6,285
  • 3
  • 31
  • 38
Vinod Ranga
  • 511
  • 8
  • 15
1

Try this simple permission library. It will handle all operations related to permission in 3 easy steps. It saved my time. You can finish all permission related work in 15 mins.

It can handle Deny, It can handle Never ask again, It can call app settings for permission, It can give a Rational message, It can give a Denial message, It can give a list of accepted permissions, It can give a list of denied permissions and etc.

https://github.com/ParkSangGwon/TedPermission

Step 1: add your dependency

dependencies {
     compile 'gun0912.ted:tedpermission:2.1.1'
     //check the above link for latest libraries
}

Step2: Ask permissions

TedPermission.with(this)
    .setPermissionListener(permissionlistener)
    .setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]")
    .setPermissions(Manifest.permission.READ_CONTACTS, Manifest.permission.ACCESS_FINE_LOCATION)
    .check();

Step 3: Handle permission response

PermissionListener permissionlistener = new PermissionListener() {
    @Override
    public void onPermissionGranted() {
        Toast.makeText(MainActivity.this, "Permission Granted", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onPermissionDenied(ArrayList<String> deniedPermissions) {
        Toast.makeText(MainActivity.this, "Permission Denied\n" + deniedPermissions.toString(), Toast.LENGTH_SHORT).show();
    }
};
Vignes
  • 390
  • 4
  • 11
1

you can listener pretty.

Listener

interface PermissionListener {
    fun onNeedPermission()
    fun onPermissionPreviouslyDenied(numberDenyPermission: Int)
    fun onPermissionDisabledPermanently(numberDenyPermission: Int)
    fun onPermissionGranted()
}

MainClass for permission

class PermissionUtil {

    private val PREFS_FILENAME = "permission"
    private val TAG = "PermissionUtil"

    private fun shouldAskPermission(context: Context, permission: String): Boolean {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            val permissionResult = ActivityCompat.checkSelfPermission(context, permission)
            if (permissionResult != PackageManager.PERMISSION_GRANTED) {
                return true
            }
        }
        return false
    }

    fun checkPermission(context: Context, permission: String, listener: PermissionListener) {

        Log.i(TAG, "CheckPermission for $permission")

        if (shouldAskPermission(context, permission)) {

            // Load history permission
            val sharedPreference = context.getSharedPreferences(PREFS_FILENAME, 0)
            val numberShowPermissionDialog = sharedPreference.getInt(permission, 0)

            if (numberShowPermissionDialog == 0) {

                (context as? Activity)?.let {
                    if (ActivityCompat.shouldShowRequestPermissionRationale(it, permission)) {
                        Log.e(TAG, "User has denied permission but not permanently")
                        listener.onPermissionPreviouslyDenied(numberShowPermissionDialog)
                    } else {
                        Log.e(TAG, "Permission denied permanently.")
                        listener.onPermissionDisabledPermanently(numberShowPermissionDialog)
                    }
                } ?: kotlin.run {
                    listener.onNeedPermission()
                }

            } else {
                // Is FirstTime
                listener.onNeedPermission()
            }


            // Save history permission
            sharedPreference.edit().putInt(permission, numberShowPermissionDialog + 1).apply()


        } else {
            listener.onPermissionGranted()
        }

    }
}

Used by this way

      PermissionUtil().checkPermission(this, Manifest.permission.ACCESS_FINE_LOCATION,
                object : PermissionListener {
                    override fun onNeedPermission() {
                        log("---------------------->onNeedPermission")

//                            ActivityCompat.requestPermissions(this@SplashActivity,
//                                    Array(1) { Manifest.permission.ACCESS_FINE_LOCATION },
//                                    118)

                    }

                    override fun onPermissionPreviouslyDenied(numberDenyPermission: Int) {
                        log("---------------------->onPermissionPreviouslyDenied")
                    }

                    override fun onPermissionDisabledPermanently(numberDenyPermission: Int) {
                        log("---------------------->onPermissionDisabled")
                    }

                    override fun onPermissionGranted() {
                        log("---------------------->onPermissionGranted")
                    }

                })

override onRequestPermissionsResult in activity or fragmnet

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
 if (requestCode == 118) {
        if (permissions[0] == Manifest.permission.ACCESS_FINE_LOCATION && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            getLastLocationInMap()
        }
        }
    }
Rasoul Miri
  • 11,234
  • 1
  • 68
  • 78
1

To answer the question precisely, What happens when user presses "Never Ask Again"?

The overridden method / function

onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray)

The grantResult array comes out to be Empty, so you can do something there maybe? But not the best practice.

How to Handle "Never Ask Again"?

I am working with Fragment, which required READ_EXTERNAL_STORAGE permission.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        when {
            isReadPermissionsGranted() -> {

                /**
                 * Permissions has been Granted
                 */

                getDirectories()
            }

            isPermissionDeniedBefore() -> {

                /**
                 * User has denied before, explain why we need the permission and ask again
                 */

                updateUIForDeniedPermissions()
                checkIfPermissionIsGrantedNow()

            }
            else -> {

                /**
                 * Need to ask For Permissions, First Time
                 */

                checkIfPermissionIsGrantedNow()

                /**
                 * If user selects, "Dont Ask Again" it will never ask again! so just update the UI for Denied Permissions
                 */

                updateUIForDeniedPermissions()

            }
        }
    }

The other functions are trivial.

// Is Read Write Permissions Granted
fun isReadWritePermissionGranted(context: Context): Boolean {
    return (ContextCompat.checkSelfPermission(
        context as Activity,
        Manifest.permission.READ_EXTERNAL_STORAGE
    ) == PackageManager.PERMISSION_GRANTED) and
            (ContextCompat.checkSelfPermission(
                context,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            ) == PackageManager.PERMISSION_GRANTED)
}

fun isReadPermissionDenied(context: Context) : Boolean {
    return ActivityCompat.shouldShowRequestPermissionRationale(
        context as Activity,
        PermissionsUtils.READ_EXTERNAL_STORAGE_PERMISSIONS)
}
devDeejay
  • 5,494
  • 2
  • 27
  • 38
1

I found to many long and confusing answer and after reading few of the answers My conclusion is

if (!ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.READ_EXTERNAL_STORAGE))
                Toast.makeText(this, "permanently denied", Toast.LENGTH_SHORT).show();
  • 3
    shouldShowRequestPermissionRationale will return true only if the permission was denied previously. So don't you think it will show "permanently denied" at the very beginning of the work flow? – Rupam Das Feb 16 '21 at 20:25
0

I would also like to obtain the information whether or not the user has selected "never ask again". I have achieved a 'almost solution' with an ugly looking flag, but before I tell you how, I will tell you about my motivation:

I would like to offer the permission referring functionality initially. If the user uses it and has no rights, he/she gets the either the 1th dialog from above or both the 2nd and 3rd. When the user has chosen 'Never ask again' I would like to disable the functionality and to display it differently. - My action is triggered by a spinner text entry, I would also like to add '(Permission revoked)' to the label text displayed. This shows to the user: 'There is functionality but I cannot use it, due to my permission settings.' However, this does not seem to be possible, as I cannot check whether or not 'Never ask again' has been chosen.

I came to a solution I can live with by having my functionality always enabled with an active permission check. I am showing a Toast message in onRequestPermissionsResult() in case of a negative response but only if I have not shown my custom rationale popup. So if the user has chosen 'Never ask again' he gets a toast message only. If the user is reluctant to chose 'never ask again' he gets only the custom rationale and the permission request popup by the operation system but not toast, as three notifications in a row would be too much pain.

0

You can use if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) method to detect whether never ask is checked or not.

For more reference : Check this

To check for multiple permissions use:

  if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)
                                || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                                || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)
                                || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
                            showDialogOK("Service Permissions are required for this app",
                                    new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            switch (which) {
                                                case DialogInterface.BUTTON_POSITIVE:
                                                    checkAndRequestPermissions();
                                                    break;
                                                case DialogInterface.BUTTON_NEGATIVE:
                                                    // proceed with logic by disabling the related features or quit the app.
                                                    finish();
                                                    break;
                                            }
                                        }
                                    });
                        }
                        //permission is denied (and never ask again is  checked)
                        //shouldShowRequestPermissionRationale will return false
                        else {
                            explain("You need to give some mandatory permissions to continue. Do you want to go to app settings?");
                            //                            //proceed with logic by disabling the related features or quit the app.
                        }

explain() method

private void explain(String msg){
        final android.support.v7.app.AlertDialog.Builder dialog = new android.support.v7.app.AlertDialog.Builder(this);
        dialog.setMessage(msg)
                .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface paramDialogInterface, int paramInt) {
                        //  permissionsclass.requestPermission(type,code);
                        startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:com.exampledemo.parsaniahardik.marshmallowpermission")));
                    }
                })
                .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface paramDialogInterface, int paramInt) {
                        finish();
                    }
                });
        dialog.show();
    }

Above code will also show dialog, which will redirect user to app settings screen from where he can give permission if had checked never ask again button.

user6435056
  • 717
  • 6
  • 5
0

I have to implement dynamic permission for camera. Where 3 possible cases occurs: 1. Allow, 2. Denied, 3. Don't ask again.

 @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

    for (String permission : permissions) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), permission)) {
            //denied
            Log.e("denied", permission);
        } else {
            if (ActivityCompat.checkSelfPermission(getActivity(), permission) == PackageManager.PERMISSION_GRANTED) {
                //allowed
                Log.e("allowed", permission);
            } else {
                //set to never ask again
                Log.e("set to never ask again", permission);
                //do something here.
            }
        }
    }
    if (requestCode != MaterialBarcodeScanner.RC_HANDLE_CAMERA_PERM) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        return;
    }
    if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        mScannerView.setResultHandler(this);
        mScannerView.startCamera(mCameraId);
        mScannerView.setFlash(mFlash);
        mScannerView.setAutoFocus(mAutoFocus);
        return;
    } else {
        //set to never ask again
        Log.e("set to never ask again", permissions[0]);
    }
    DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            dialog.cancel();
        }
    };
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    builder.setTitle("Error")
            .setMessage(R.string.no_camera_permission)
            .setPositiveButton(android.R.string.ok, listener)
            .show();


}

private void insertDummyContactWrapper() {
        int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.CAMERA);
        if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.CAMERA},
                    REQUEST_CODE_ASK_PERMISSIONS);
            return;
        }
        mScannerView.setResultHandler(this);
        mScannerView.startCamera(mCameraId);
        mScannerView.setFlash(mFlash);
        mScannerView.setAutoFocus(mAutoFocus);
    }

private int checkSelfPermission(String camera) {
    if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
        return REQUEST_CODE_ASK_PERMISSIONS;
    } else {
        return REQUEST_NOT_CODE_ASK_PERMISSIONS;
    }
}
Federico Navarrete
  • 3,069
  • 5
  • 41
  • 76
hitesh141
  • 963
  • 12
  • 25
0

Expanding on mVck's answer above, the following logic determines whether "Never ask again" has been checked for a given Permission Request:

bool bStorage = grantResults[0] == Permission.Granted;
bool bNeverAskForStorage =
    !bStorage && (
        _bStorageRationaleBefore == true  && _bStorageRationaleAfter == false ||
        _bStorageRationaleBefore == false && _bStorageRationaleAfter == false
    );

which is excerpted from below (for the full example see this answer)

private bool _bStorageRationaleBefore;
private bool _bStorageRationaleAfter;        
private const int ANDROID_PERMISSION_REQUEST_CODE__SDCARD = 2;
//private const int ANDROID_PERMISSION_REQUEST_CODE__CAMERA = 1;
private const int ANDROID_PERMISSION_REQUEST_CODE__NONE = 0;

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
{
    base.OnRequestPermissionsResult(requestCode, permissions, grantResults);

    switch (requestCode)
    {
        case ANDROID_PERMISSION_REQUEST_CODE__SDCARD:               
            _bStorageRationaleAfter = ShouldShowRequestPermissionRationale(Android.Manifest.Permission.WriteExternalStorage);
            bool bStorage = grantResults[0] == Permission.Granted;
            bool bNeverAskForStorage =
                !bStorage && (
                    _bStorageRationaleBefore == true  && _bStorageRationaleAfter == false ||
                    _bStorageRationaleBefore == false && _bStorageRationaleAfter == false
                );      
            break;                
    }
}

private List<string> GetRequiredPermissions(out int requestCode)
{
    // Android v6 requires explicit permission granting from user at runtime for security reasons            
    requestCode = ANDROID_PERMISSION_REQUEST_CODE__NONE; // 0
    List<string> requiredPermissions = new List<string>();

    _bStorageRationaleBefore = ShouldShowRequestPermissionRationale(Android.Manifest.Permission.WriteExternalStorage);
    Permission writeExternalStoragePerm = ApplicationContext.CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage);
    //if(extStoragePerm == Permission.Denied)
    if (writeExternalStoragePerm != Permission.Granted)
    {
        requestCode |= ANDROID_PERMISSION_REQUEST_CODE__SDCARD;
        requiredPermissions.Add(Android.Manifest.Permission.WriteExternalStorage);
    }

    return requiredPermissions;
}

protected override void OnCreate(Bundle savedInstanceState)
{
    base.OnCreate(savedInstanceState);

        // Android v6 requires explicit permission granting from user at runtime for security reasons
        int requestCode;
        List<string> requiredPermissions = GetRequiredPermissions(out requestCode);
        if (requiredPermissions != null && requiredPermissions.Count > 0)
        {
            if (requestCode >= ANDROID_PERMISSION_REQUEST_CODE__SDCARD)                    
            {
                _savedInstanceState = savedInstanceState;
                RequestPermissions(requiredPermissions.ToArray(), requestCode);
                return;
            }
        }
    }            

    OnCreate2(savedInstanceState);
}
samus
  • 6,102
  • 6
  • 31
  • 69
0

This sample demonstrates how to handle when the user selects "DENY & DON'T ASK AGAIN"

enter image description here

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    registerStoragePermission()
    registerGalleryLauncher()

    registerCameraPermission()
    registerCameraLauncher()
}

private fun registerCameraPermission() {
    requestCameraPermissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
            if (granted) {
                Log.d(TAG, "registerCameraPermission - Camera Permission Granted")
                openCamera()
            } else {
                Log.d(TAG, "registerCameraPermission - Camera Permission NOT Granted")
                requestCameraPermission()
            }
        }
}

private fun registerStoragePermission() {
    requestStoragePermissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
            if (granted) {
                Log.d(TAG, "registerStoragePermission - Storage Permission Granted")
                viewGallery()
            } else {
                Log.d(TAG, "registerStoragePermission - Storage Permission NOT Granted")
                requestStoragePermission()
            }
        }
}

private fun registerCameraLauncher() {
    cameraLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            if (result.resultCode == Activity.RESULT_OK) {
                val data: Intent? = result.data
                if (data == null) {
                    return@registerForActivityResult
                }
                val extras = data.extras
                imageBitmap = extras!!["data"] as Bitmap
                file = FileUtils.createFile(requireContext(),
                    getString(R.string.app_name),
                    "my_profile_image.png"
                )
                //FileUtils.saveBitmap(imageBitmap, file);
                val imageLocalPath = FileUtils.saveImageToInternalStorage(file, imageBitmap)

                SharedPreferencesUtils.setProfilePath(requireActivity(), imageLocalPath)
                profileFragmentBinding.imageViewCircleNoStroke.setImageBitmap(imageBitmap)
                profileFragmentBinding.imageViewCircleNoStroke.setScaleType(ImageView.ScaleType.CENTER_CROP)
            }
        }
}

private fun registerGalleryLauncher() {
    galleryLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
            if (result.resultCode == Activity.RESULT_OK) {
                val data: Intent? = result.data
                if (data == null) {
                    return@registerForActivityResult
                }
                val uri = data.data
                var imageLocalPath = File(FileUtils.getPathReal(requireActivity(), uri!!))

                file = imageLocalPath.absoluteFile

                SharedPreferencesUtils.setProfilePath(requireActivity(), imageLocalPath.absolutePath)
                Glide.with(requireActivity()).load(uri)
                    .into(profileFragmentBinding.imageViewCircleNoStroke)
                profileFragmentBinding.imageViewCircleNoStroke.setScaleType(ImageView.ScaleType.CENTER_CROP)
            }
        }
}

private fun showImageUploadOptions() {
    val mDialog = activity.let { Dialog(it!!) }
    mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
    mDialog.setContentView(R.layout.dialog_profile_image_option)
    mDialog.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
    //val mAlertMessageTv = mDialog.findViewById<View>(R.id.id_alert_tv) as TextView
    //mAlertMessageTv.text = message
    galleryLl = mDialog.findViewById<View>(R.id.id_gallery_ll) as LinearLayout
    cameraLl = mDialog.findViewById<View>(R.id.id_camera_ll) as LinearLayout
    removePhotoLl = mDialog.findViewById<View>(R.id.id_remove_photo_ll) as LinearLayout

    galleryLl.setOnClickListener {
        CallStoragePermission()
        mDialog.dismiss()
    }

    cameraLl.setOnClickListener {
        CallCameraPermission()
        mDialog.dismiss()
    }

    removePhotoLl.setOnClickListener {
        CallRemovePhoto()
        mDialog.dismiss()
    }

    mDialog.setCancelable(true)
    mDialog.show()
    val metrics = resources.displayMetrics
    val width = metrics.widthPixels
    val height = metrics.heightPixels
    mDialog.window!!.setLayout(
        width,
        LinearLayout.LayoutParams.WRAP_CONTENT
    )

}

fun CallStoragePermission() {

    if (!Status_checkReadExternalStoragePermission()) {
        requestStoragePermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
    } else {
        viewGallery()
    }
}

private fun Status_checkReadExternalStoragePermission(): Boolean {
    val permissionState = ActivityCompat.checkSelfPermission(
        requireActivity(),
        Manifest.permission.READ_EXTERNAL_STORAGE
    )
    return permissionState == PackageManager.PERMISSION_GRANTED
}

private fun requestCameraPermission() {

    when {
        ContextCompat.checkSelfPermission(
            requireContext(),
            Manifest.permission.CAMERA
        ) == PackageManager.PERMISSION_GRANTED -> {

            Log.d(TAG, "requestCameraPermission - Camera Permission Granted")
            openCamera()

            // The permission is granted
            // you can go with the flow that requires permission here
        }
        shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
            // This case means user previously denied the permission
            // So here we can display an explanation to the user
            // That why exactly we need this permission
            Log.d(TAG, "requestCameraPermission - Camera Permission NOT Granted")
            showPermissionAlert(
                getString(R.string.camera_permission),
                getString(R.string.camera_permission_denied),
                getString(R.string.ok_caps),
                getString(R.string.cancel_caps)
            ) { requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA) }
        }
        else -> {
            // Everything is fine you can simply request the permission

            showPermissionAlert(
                getString(R.string.camera_permission),
                getString(R.string.camera_permission_denied),
                getString(R.string.settings_caps),
                getString(R.string.cancel_caps)
            ) {
                val intent = Intent()
                intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
                val uri = Uri.fromParts(
                    "package",
                    BuildConfig.APPLICATION_ID, null
                )
                intent.data = uri
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                startActivity(intent)
            }

        }
    }
}


private fun requestStoragePermission() {

    when {
        ContextCompat.checkSelfPermission(
            requireContext(),
            Manifest.permission.READ_EXTERNAL_STORAGE
        ) == PackageManager.PERMISSION_GRANTED -> {

            Log.d(TAG, "requestStoragePermission - Storage Permission Granted")
            viewGallery()

            // The permission is granted
            // you can go with the flow that requires permission here
        }
        shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE) -> {
            // This case means user previously denied the permission
            // So here we can display an explanation to the user
            // That why exactly we need this permission
            Log.d(TAG, "requestStoragePermission - Storage Permission NOT Granted")
            showPermissionAlert(
                getString(R.string.read_storage_permission_required),
                getString(R.string.storage_permission_denied),
                getString(R.string.ok_caps),
                getString(R.string.cancel_caps)
            ) { requestStoragePermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE) }
        }
        else -> {
            // Everything is fine you can simply request the permission

            showPermissionAlert(
                getString(R.string.read_storage_permission_required),
                getString(R.string.storage_permission_denied),
                getString(R.string.settings_caps),
                getString(R.string.cancel_caps)
            ) {
                val intent = Intent()
                intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
                val uri = Uri.fromParts(
                    "package",
                    BuildConfig.APPLICATION_ID, null
                )
                intent.data = uri
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                startActivity(intent)
            }

        }
    }
}

private fun showPermissionAlert(
    title: String,
    message: String,
    ok: String,
    cancel: String,
    function: () -> Unit
) {
    val mDialog = requireActivity().let { Dialog(it) }
    mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
    mDialog.setContentView(R.layout.dialog_permission_alert)
    mDialog.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))

    val mTitleTv = mDialog.findViewById<View>(R.id.id_title_tv) as AppCompatTextView
    mTitleTv.text = title

    val mMessageTv = mDialog.findViewById<View>(R.id.id_message_tv) as AppCompatTextView
    mMessageTv.text = message

    val mNoBtn = mDialog.findViewById<View>(R.id.no_btn) as AppCompatTextView
    mNoBtn.text = cancel

    val mYesBtn = mDialog.findViewById<View>(R.id.yes_btn) as AppCompatTextView
    mYesBtn.text = ok

    mYesBtn.setOnClickListener {
        function.invoke()
        mDialog.dismiss()
    }

    mNoBtn.setOnClickListener { mDialog.dismiss() }

    mDialog.setCancelable(true)
    mDialog.show()
    val metrics = resources.displayMetrics
    val width = metrics.widthPixels
    val height = metrics.heightPixels
    mDialog.window!!.setLayout(
        width,
        LinearLayout.LayoutParams.WRAP_CONTENT
    )
}

fun viewGallery() {
    val intentDocument = Intent(Intent.ACTION_GET_CONTENT)
    intentDocument.type = "image/*"
    intentDocument.putExtra(
        Constants.REQUEST_CODE,
        Constants.REQUEST_PHOTO_FROM_GALLERY
    )
    galleryLauncher.launch(intentDocument)
}

fun openCamera() {
    val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
    takePictureIntent.putExtra(
        Constants.REQUEST_CODE,
        Constants.REQUEST_PERMISSIONS_REQUEST_CODE_CAMERA
    )
    cameraLauncher.launch(takePictureIntent)
}

fun CallCameraPermission() {
    if (!Status_checkCameraPermission()) {
        requestCameraPermissionLauncher.launch(Manifest.permission.CAMERA)
    } else {
        openCamera()
    }
}

private fun Status_checkCameraPermission(): Boolean {
    val camera = ActivityCompat.checkSelfPermission(
        requireActivity(),
        Manifest.permission.CAMERA
    )

    return camera == PackageManager.PERMISSION_GRANTED
}
Rasool Mohamed
  • 433
  • 7
  • 17
-1

I am bit too late, I have faced similar issue. Resolved this issue as below

Suppose you want location permission

Request permission launcher
private final ActivityResultLauncher<String> requestPermissionLauncher =
      registerForActivityResult(
          new RequestPermission(),
          isGranted -> {
            if (isGranted) {
              // Permission is granted go ahead
            } else {
              shouldShowRequestPermissionRationale();
            }
          });
Permission check
  private boolean hasPermissions() {
        if (checkSelfPermission(requireActivity(), ACCESS_FINE_LOCATION) == PERMISSION_GRANTED) {
          // Permission is granted go ahead
        } else {
          requestPermissionLauncher.launch(ACCESS_FINE_LOCATION);
        }
      }
Check if need to show permission rational/custom dialog to educate user
private void shouldShowRequestPermissionRationale() {
        if (!shouldShowRequestPermissionRationale(ACCESS_FINE_LOCATION)) {
          // need to show permission rational custom dialog.
        } 
    }
Narendra
  • 609
  • 1
  • 12
  • 28