ChatGPT解决这个技术问题 Extra ChatGPT

How Do We Distinguish Never-Asked From Stop-Asking in Android M's Runtime Permissions?

When it comes to the M Developer Preview runtime permissions, according to Google:

If you have never asked for a certain permission before, just ask for it If you asked before, and the user said "no", and the user then tries doing something that needs the rejected permission, you should prompt the user to explain why you need the permission, before you go on to request the permission again If you asked a couple of times before, and the user has said "no, and stop asking" (via the checkbox on the runtime permission dialog), you should just stop bothering (e.g., disable the UI that requires the permission)

However, we only have one method, shouldShowRequestPermissionRationale(), returning a boolean, and we have three states. We need a way to distinguish the never-asked state from the stop-asking state, as we get false from shouldShowRequestPermissionRationale() for both.

For permissions being requested on first run of the app, this is not a big problem. There are plenty of recipes for determining that this is probably the first run of your app (e.g., boolean value in SharedPreferences), and so you assume that if it's the first run of your app, you're in the never-asked state.

However, part of the vision of runtime permissions is that you might not ask for all of them up front. Permissions tied to fringe features you might only ask for later on, when the user taps on something that requires that permission. Here, the app may have been run many times, for months, before we all of a sudden need to request another permission.

In those cases, are we supposed to track whether or not we asked for the permission ourselves? Or is there something in the Android M API that I am missing that tells us whether we asked before or not?

This is all info I got, same as what you just posted plus.google.com/+BenjaminWeiss/posts/PFSd7wau4n8
The sample app is so trivial and incomplete it might as well not exist.
So best guess is to store a boolean in SharedPreferences per permission, or any similar artifact, which was Google's recommendation at IO.
My concern is the possibility of the SharedPreferences getting out of sync with Android's own stored permission information. Android is the "system of record" with regards to runtime permissions. It clearly has the information, as otherwise it could not ever return true from shouldShowRequestPermissionRationale(). I'm just seeing if there's some method that got added that I'm missing, that's all.
Knowing Google, they will deprecate shouldShowRequestPermissionRationale() in 6.1 and add a new method that returns an int.

N
Nicks

I know I am posting very late, but detailed example may be helpful 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 select s “never asks again.

Here is an example with multiple permission request:-

The app needs 2 permissions at startup . SEND_SMS and ACCESS_FINE_LOCATION (both are mentioned in manifest.xml).

As soon as the app starts up, it asks for multiple permissions together. If both permissions are granted the normal flow goes.

https://i.stack.imgur.com/TmOCS.png

public static final int REQUEST_ID_MULTIPLE_PERMISSIONS = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if(checkAndRequestPermissions()) {
        // carry on the normal flow, as the case of  permissions  granted.
    }
}

private  boolean checkAndRequestPermissions() {
    int permissionSendMessage = ContextCompat.checkSelfPermission(this,
            Manifest.permission.SEND_SMS);
    int locationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
    List<String> listPermissionsNeeded = new ArrayList<>();
    if (locationPermission != PackageManager.PERMISSION_GRANTED) {
        listPermissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
    }
    if (permissionSendMessage != PackageManager.PERMISSION_GRANTED) {
        listPermissionsNeeded.add(Manifest.permission.SEND_SMS);
    }
    if (!listPermissionsNeeded.isEmpty()) {
        ActivityCompat.requestPermissions(this, listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]),REQUEST_ID_MULTIPLE_PERMISSIONS);
        return false;
    }
    return true;
}

In case one or more permissions are not granted, activityCompat.requestPermissions() will request permissions and the control goes to onRequestPermissionsResult() callback method.

You should check the value of shouldShowRequestPermissionRationale() flag in onRequestPermissionsResult() callback method.

There are only two cases:--

Case 1:-Any time user clicks Deny permissions (including the very first time), it will return true. So when the user denies, we can show more explanation and keep asking again.

Case 2:-Only if user select “never asks again” it will return false. In this case, we can continue with limited functionality and guide user to activate the permissions from settings for more functionalities, or we can finish the setup, if the permissions are trivial for the app.

CASE- 1

https://i.stack.imgur.com/nbTo6.png

CASE- 2

https://i.stack.imgur.com/rQqYp.png

@Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        Log.d(TAG, "Permission callback called-------");
        switch (requestCode) {
            case REQUEST_ID_MULTIPLE_PERMISSIONS: {

                Map<String, Integer> perms = new HashMap<>();
                // Initialize the map with both permissions
                perms.put(Manifest.permission.SEND_SMS, PackageManager.PERMISSION_GRANTED);
                perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);
                // Fill with actual results from user
                if (grantResults.length > 0) {
                    for (int i = 0; i < permissions.length; i++)
                        perms.put(permissions[i], grantResults[i]);
                    // Check for both permissions
                    if (perms.get(Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_GRANTED
                            && perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
                        Log.d(TAG, "sms & location services permission granted");
                        // process the normal flow
                        //else any one or both the permissions are not granted
                    } else {
                            Log.d(TAG, "Some permissions are not granted ask again ");
                            //permission is denied (this is the first time, when "never ask again" is not checked) so ask again explaining the usage of permission
//                        // shouldShowRequestPermissionRationale will return true
                            //show the dialog or snackbar saying its necessary and try again otherwise proceed with setup.
                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.SEND_SMS) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
                                showDialogOK("SMS and Location Services Permission 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.
                                                        break;
                                                }
                                            }
                                        });
                            }
                            //permission is denied (and never ask again is  checked)
                            //shouldShowRequestPermissionRationale will return false
                            else {
                                Toast.makeText(this, "Go to settings and enable permissions", Toast.LENGTH_LONG)
                                        .show();
    //                            //proceed with logic by disabling the related features or quit the app.
                            }
                    }
                }
            }
        }

    }

    private void showDialogOK(String message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(this)
                .setMessage(message)
                .setPositiveButton("OK", okListener)
                .setNegativeButton("Cancel", okListener)
                .create()
                .show();
    }

Problem is that shouldShowRequestPermissionRationale returns false if they have checked "Don't ask again" OR if they have never denied before. So checking the return value of this method is not good enough to know whether or not we should educate up front.
How we can use this solution? shouldShowRequestPermissionRationale() requires minSdkVersion 23
@NaPro This feature(runtime permissions) came in API23 only. And backward compatibility is supported so minSdkVersion , so shudnt be a prblem for you unless u have compileSDK version 23 or greater.
very nice. saved my day.
hey @Nicks can you please tell me that what that for loop is doing there what is the use of this for (int i = 0; i < permissions.length; i++)
M
MLProgrammer-CiM

As per the current example: https://github.com/googlesamples/android-RuntimePermissions/blob/master/Application/src/main/java/com/example/android/system/runtimepermissions/MainActivity.java#L195

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
        int[] grantResults) {
    if (requestCode == REQUEST_CAMERA) {
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            doThing();
            //STORE FALSE IN SHAREDPREFERENCES
        } else {
            //STORE TRUE IN SHAREDPREFERENCES
        }
    }

Store a boolean in SharedPreferences with key as your permission code and value as indicated above, to indicate whether that preference has been denied before.

Sadly, you probably can't check against a preference that has been accepted and later denied while your app is running. The final spec is not available, but there's a chance that your app either gets restarted or gets mock values until the next launch.


"you probably can't check against a preference that has been accepted and later denied while your app is running" -- I'm under the impression that changes to permissions in Settings results in your app's process being terminated, thereby in effect forcing you check to see if you have the permissions again. As noted in a comment on the question, I'm hoping that there's some mechanism for this in Android proper, to avoid duplication of data and the risk of getting out of sync. Thanks!
I believe there's different behavior between apps compiled against target < M and > M. You'll get mock values for older versions, and SecurityExceptions from the others.
I tried this mechanism but alas, it didn't cover all scenarios. If you allow permission within app and then go to permission settings and deny it, the boolean stored in shared preferences is still true (from before) where you need it to be false. shouldShowRequestPermissionRationale is fine if you don't plan to ever educate up front but, if you do, it's no good. There should have been a "shouldEducateUpFront" method to determine cases where a user has not previously denied a permission. Especially since Google recommend doing so in some cases: youtube.com/watch?v=iZqDdvhTZj0
e
ed9er

No, you don't need to track whether or not you asked for the permission, and you don't need to distinguish Never-Asked From Stop-Asking.

The state 1 and 3 are the same for app developer: you need the permission and ActivityCompat.checkSelfPermission != PackageManager.PERMISSION_GRANTED, then you just ask for the permission via ActivityCompat.requestPermissions(), whenever user tapped the feature that requires the permission, no matter how many times you have requested. User will eventually "Grant" it, or "Deny" it with "never ask again" checked. The design does NOT discourage you from popup the permission request dialogbox multiple times.

However, the design does encourage you to explain the purpose of the permission at some point - your state 2. shouldShowRequestPermissionRationale() is NOT used to determine if you should request for permission, it's used to determine if you should show explanations, BEFORE you request for permission.

A couple more explanation regarding the state 3:

Yes, we should stop bothering user by stop showing the explanation, not stop request. That's why they provided the shouldShowRequestPermissionRationale(). It's not bothering to keep request for permission. After user chose "never ask again", ActivityCompat.requestPermissions() will not popup dialogbox anymore. It's better to disable the relevant UI every time we find out we don't have the permission, during single user session. Instead of disable the UI after shouldShowRequestPermissionRationale() return false.


"It's better to disable the relevant UI every time we find out we don't have the permission, during single user session. Instead of disable the UI after shouldShowRequestPermissionRationale() return false" -- this is the core of our disagreement. We have a similar problem if we use Google's approach of not disabling the UI but showing a snackbar that leads the user to Settings. Somehow we need to know to show that snackbar, and the straightforward solution would require us to distinguish state 3 from state 1.
The good news is that shouldShowRequestPermissionRationale() seems to be returning the expected value already in onRequestPermissionsResult(), so if the user denied with don't-ask-again, shouldShowRequestPermissionRationale() does indeed return false. So if you want to have the same response (e.g., show a snackbar) regardless of whether the user just denied with don't-ask-again or did so previously, you do not need state 1. If you want different responses (e.g., only show a snackbar if the user denied with don't-ask-again some time ago, not just now), you would still need state 1.
@CommonsWare This definitely helps a lot! Even though this seems to be a bit hacky. Hope this will still work in future releases.
Be aware that shouldShowRequestPermissionRationale() will return false if you never showed the permission dialog before, even if the user didn't denied the permission ever before. So the first time you should force the permission dialog and then you can rely on this. Take in account that this behavior can change in the future.
J
Joacer

I have an approach to the solution to your problem, it seems to work pretty well for me.

I Distinguish Never-Asked From Stop-Asking using the SharedPreferences, I'll give you an example of how I use that.

private void requestAccountPermission() {

        SharedPreferences mPreferences = getSharedPreferences("configuration", MODE_PRIVATE);
        boolean firstTimeAccount = mPreferences.getBoolean("firstTimeAccount", true);

        if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.GET_ACCOUNTS)) {
            // 2. Asked before, and the user said "no"
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.GET_ACCOUNTS}, REQUEST_CODE_ACCOUNTS);
        }else {
            if(firstTimeAccount) { 
                // 1. first time, never asked 
                SharedPreferences.Editor editor = mPreferences.edit();
                editor.putBoolean("firstTimeAccount", false);
                editor.commit();

                // Account permission has not been granted, request it directly.
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.GET_ACCOUNTS},REQUEST_CODE_ACCOUNTS);
            }else{
                // 3. If you asked a couple of times before, and the user has said "no, and stop asking"

                // Your code
            }
        }
    }

ActivityCompat.requestPermissions will not show after we click never ask again? And it go to the else condition. Is there way to make it show again? Just like facebook.
Y
Yogesh Pareek

Here is method to track when permission dialog was shown first time, when user checked the never ask again and when permission is directly denied after user checked never ask again for this we need to keep a flag for if permission rationale dialog has been shown before getting result in onRequestPermissionsResult. Call method checkPermission() when required.

public boolean mPermissionRationaleDialogShown = false;

public void checkPermission() {
    if (ContextCompat.checkSelfPermission(this, "PermissionName")
            != PackageManager.PERMISSION_GRANTED) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName")) {
            showPermissionRequiredDialog();
        } else {
            askPermission();
        }
    } else {
       // Permission Granted
    }
}

public void askPermission() {
    ActivityCompat.requestPermissions(this,
            new String[]{"PermissionName"}, permissionRequestCode);
}

public void showPermissionRequiredDialog() {
    mPermissionRationaleDialogShown = true;
    // Dialog to show why permission is required
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == PERMISSION_REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Permission Granted
        } else {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName")
                    && !mPermissionRationaleDialogShown) {
                // Permission dialog was shown for first time
            } else if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName")
                    && mPermissionRationaleDialogShown){
                // User deny permission without Never ask again checked
            } else if (!ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_READ_EXTERNAL)
                    && mPermissionRationaleDialogShown) {
                // User has checked Never ask again during this permission request
            } else {
                // No permission dialog shown to user has user has previously checked Never ask again. Here we can show dialog to open setting screen to change permission
            }
        }
    } else {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

N
Natwar Singh

After trying all the answer here and some other post over internet. I came to know that I have to use a sharedPreference isLocationPermissionDialogShown (default false) and every thing works as per expected.

If first time asked for permission. In this case shouldShowRequestPermissionRationale returns false and isLocationPermissionDialogShown also false Second time shouldShowRequestPermissionRationale return true and while showing dialog we set isLocationPermissionDialogShown to true. and when we check condition both will be true Every Time until Never Ask Again ticked shouldShowRequestPermissionRationale return true and isLocationPermissionDialogShown returns true If Never Ask Again ticked shouldShowRequestPermissionRationale return false and isLocationPermissionDialogShown returns true. Which is what we need.

Please check working example.

public class MainActivity extends AppCompatActivity {
    SharedPreferences sharedPreferences;
    String locationPermission;
    String prefLocationPermissionKey = "isLocationPermissionDialogShown";
    private final int PERMISSION_REQUEST_CODE_LOCATION = 1001;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        locationPermission = Manifest.permission.ACCESS_FINE_LOCATION;
        sharedPreferences = getSharedPreferences("configuration", MODE_PRIVATE);

        //check for android version
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //Check for permission
            if (checkSelfPermission(locationPermission) != PackageManager.PERMISSION_GRANTED) {
                //check if clarification dialog should be shown.
                if (shouldShowRequestPermissionRationale(locationPermission)) {
                    showClarificationDialog(locationPermission, PERMISSION_REQUEST_CODE_LOCATION);
                } else  {
                    requestPermissions(new String[] { locationPermission}, PERMISSION_REQUEST_CODE_LOCATION);
                }
            } else {
                Log.d("nets-debug", "permission already grranted");
            }
        }

    }

    @Override
    @TargetApi(Build.VERSION_CODES.M)
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
            //for location permission
            if (requestCode == PERMISSION_REQUEST_CODE_LOCATION) {
                boolean isLocationPermissionDialogShown = sharedPreferences.getBoolean(prefLocationPermissionKey, false);

                if (!shouldShowRequestPermissionRationale(locationPermission) && isLocationPermissionDialogShown) {
                    // user selected Never Ask Again. do something
                    Log.d("nets-debug", "never ask again");
                } else {
                    // all other conditions like first time asked, previously denied etc are captured here and can be extended if required.
                    Log.d("nets-debug", "all other cases");
                }
            }

        }

    }

    @TargetApi(Build.VERSION_CODES.M)
    public void showClarificationDialog(final String permission, final int requestCode) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Permission Required");
        builder.setMessage("Please grant Location permission to use all features of this app");
        builder.setPositiveButton("Grant", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                SharedPreferences.Editor editor = sharedPreferences.edit();
                editor.putBoolean(prefLocationPermissionKey, true);
                editor.apply();
                requestPermissions(new String[] {permission}, requestCode);
            }
        });
        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(getApplicationContext(), "This permission required", Toast.LENGTH_LONG).show();
            }
        });
        builder.create().show();
    }

}

Hope this will help.


S
Shlomi

Regarding MLProgrammer-CiM's answer, I have an idea of how to solve the scenario in which the user revokes the permission after the boolean stored in SharedPrefrences is already true,

simply create another constant boolean, if the first one called for example: Constant.FIRST_TIME_REQUEST (which its default state will be true) the second one will be called Constant.PERMISSION_ALREADY_GRANTED (which will be false on default)

On onRequestPermissionsResult if permission was granted you change its value to true, of course.

Now, in the part where you want to ask permission with pre-explanation, write something like that:

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
   SharedPreferences sp = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
   boolean isPermissionGranted = sp.getBoolean(Constant.PERMISSION_ALREADY_GRANTED, false);
   if (isPermissionGranted) {
      sp.putBoolean(Constant.PERMISSION_ALREADY_GRANTED, false);
      sp.putBoolean(Constant.FIRST_TIME_REQUEST, true);
   }

   if (ActivityCompat.shouldShowRequestPermissionRationale(activity, androidPermissionName) || sp.getBoolean(Constant.FIRST_TIME_REQUEST, true) ) {
   showDialogExplanation();
}
}

that way even if the user will remove the permission, the boolean will be set to false again.

good luck, I hope it'll help.

Shlo


d
debo.stackoverflow

SO FINALLY MY TIME HAS COME TO ANSWER A QUESTION FROM COMMONSWARE

Business flow:-

1. When user clicks on "deny permission" for the first time, I will show rationale dialog to explain the necessity of the permission. Then if the user clicks on "cancel" button on the rationale dialog, I will show a toast showing message "Please give permission to get location".

2. After that when user clicks on deny permission(dont ask again) on the permissions dialog, I will show a message "Please give location permission from app settings". Notice that I have added the words "from app settings" because the user has checked the box of "dont ask again".

3. So from now on the permission dialog will not be shown.Also the rationale dialog will not be shown.

So the key here is that if both permission dialog and rationale dialog is not shown, then it means that user has checked the "dont ask again" checkbox.

The code:-

        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            if(ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.ACCESS_FINE_LOCATION)){
                AlertDialogHelper.showDialogWithYesNoCallback(mContext, getString(R.string.confirm), getString(R.string.please_give_permission_to_get_location), new onItemClickReturnBoolean() {
                    @Override
                    public void onItemClick(Boolean status) {
                        if(status){
                            ActivityCompat.requestPermissions(SplashScreenActivity.this,permissions,AppConfig.FINE_LOCATION_PERMISSION_REQUEST_CODE);
                        }
                        else{
                            ShowToast.showShortToast(SplashScreenActivity.this,getString(R.string.please_give_permission_to_get_location));
                            finish();
                        }
                    }
                });
            }
            else{
                ActivityCompat.requestPermissions(this,permissions,AppConfig.FINE_LOCATION_PERMISSION_REQUEST_CODE);
            }
        }
        else{
            gettingLocationAfterPermissionGranted();
        }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode == AppConfig.FINE_LOCATION_PERMISSION_REQUEST_CODE){
            if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
                gettingLocationAfterPermissionGranted();
            }
            else{
                if(ActivityCompat.shouldShowRequestPermissionRationale(SplashScreenActivity.this,Manifest.permission.ACCESS_FINE_LOCATION)){
                    ShowToast.showShortToast(this,getString(R.string.please_give_permission_to_get_location));
                }
                else{
                    ShowToast.showShortToast(this,getString(R.string.please_give_location_permission_from_app_settings));
                }
                finish();
            }
        }
    }

Check this repository: https://github.com/debChowdhury/PermissionHelperEasy

Easy peasy


R
Ronnie

You can look here - there is a flowchart that explains the process quite good. It also explains when you should call shouldShowRequestPermissionRationale() and when it returns true.

Basically according to Android's documentation, you should always ask for permission if you don't have it (Android will automatically return DENIED in the callback if the user said to never ask again) and you should display a short message if the user has already declined you once in the past but hasn't marked the never ask again option.


This does not address the question.
C
Community

There is no need to create a parallel persistent state for the permission state, you could just use this method which returns the current permission state at any time:

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

    public static final int GRANTED = 0;
    public static final int DENIED = 1;
    public static final int BLOCKED = 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;
            }
            return DENIED;
        }
        return GRANTED;
    }

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

I also used this answered here.


This will fail for the first time use case, if you've never asked the user for a permission, your method getPermissionStatus will incorrectly return BLOCKED, which is actually not true. A 4th state is required in this design, probably called HAVENT_ASKED, and the only way to detect that is through use of a shared pref or something similar.
true, still maybe useful for some use cases; I just really dont like having a parallel state tracked with shared prefs