ChatGPT解决这个技术问题 Extra ChatGPT

Android M Permissions : Confused on the usage of shouldShowRequestPermissionRationale() function

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

But how can we differentiate between the following two cases?

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

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

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

The accepted answer is good. Just as an alternative you could also use a shared pref to know if the app has requested the permission before. Just throwing that out there in case it's more applicable for someone else's situation.
There is a case 3 as well: The user has been asked for and granted/denied the permission, but has used the permission settings to revert back to "ask every time". Testing shows shouldShowRequestPermissionRationale() returns false in this case, which will hurt any code relying on a "have I asked before" flag.
here is a google sample showing the best practices in permissions on Android. github.com/android/permissions-samples
@itabdullah Google's sample code is useless since they didn't even consider the highly likely usecase of "did the user perma-deny the permission last time". :-/ typical

T
Tim

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

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

So the logic should be like this:

Request permission: if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); } else { //Do the stuff that requires permission... } Check if the permission was denied or granted in onRequestPermissionsResult. If the permission was denied previously, this time there will be a Never ask again checkbox in the permission dialog. Call shouldShowRequestPermissionRationale to see if the user checked Never ask again. shouldShowRequestPermissionRationale method returns false only if the user selected Never ask again or device policy prohibits the app from having that permission: if (grantResults.length > 0){ if(grantResults[0] == PackageManager.PERMISSION_GRANTED) { //Do the stuff that requires permission... }else if (grantResults[0] == PackageManager.PERMISSION_DENIED){ // Should we show an explanation? if (ActivityCompat.shouldShowRequestPermissionRationale(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { //Show permission explanation dialog... }else{ //Never ask again selected, or device policy prohibits the app from having that permission. //So, disable that feature, or fall back to another situation... } } }

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


One point of clarification, shouldShowRequestPermissionRationale() willl also return false if the user has has never been asked asked for the permission (ie the first time the application is run). You would not run into that case if you follow the logic of the example provided. But the wording, under 2 is a little misleading.
I'm not sure, this seems flawed. How are we supposed to know if it is the first time the user gets asked? I have to track if the user got asked, and if he did, then I have to reverse the logic. Doesn't make any sense to me.
I think it's worth noting that where you are passing context in ActivityCompat.shouldShowRequestPermissionRationale(...) the parameter is actually of type Activity. May not affect you all but in my case it does.
This android logic is so darn stupid! It FORCES me to call the should in the callback AND save its counter-value in NVM just to know if I need to prompt the request again the next time the app opens! ... wow (facepalm) ... was it too difficult to make only one call returning a status enumeration??
I think this is a big fail by Google. The official documentation states that the shouldShowRequestPermissionRationale() should be called before checking the permissions (see developer.android.com/training/permissions/requesting#explain), but all answers in StackOverflow call it in onRequestPermissionResult() in order to distinguish whether the user clicked "Never ask again" or not.
m
muthuraj

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

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

And the PreferenceUtil methods are as follows.

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

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

Here is an example,

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

Case 1: The app doesn't have a permission and the user has not been asked for the permission before. In this case, shouldShowRequestPermissionRationale() will return false because this is the first time we're asking the user. Case 2: The user has denied the permission and selected "Don't ask again", in this case too shouldShowRequestPermissionRationale() will return false. I would want to send the user to the App's settings page in Case 2. How do i go about differentiating these two cases?

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

Happy coding :)


Excellent explanation bro. Followed your exact same procedure. :)
What do I fill in for thisActivity? public void onPermissionAsk() { ActivityCompat.requestPermissions( thisActivity, ... .
@Mardymar thisActivity is nothing but YourActivity.this.
how to handle multiple permissions and how to integrate this code inside fragment.
thank you - out of all the answers (including the accepted/most upvoted one) you are the only one who actually answered the question
S
Shumoapp

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

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

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

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

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


C
Community

UPDATE

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

==

My original answer:

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

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


Yes even I agree that CanC's method is the one that should be followed. I'm going to mark it as the accepted answer.
W
Wei WANG

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

Then you don't have to handle shouldShowRequestPermissionRationale directly.


why i didn't see this project prevoiusly :)
The problem with EasyPermissions stays almost the same. Asking permissionPermanentlyDenied internally just calls shouldShowPermissionsRationale and returns true in the case when the user never was requested to grant permissions.
b
bmjohns

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

PermissionUtil

object PermissionUtil {
    private val PREFS_FILE_NAME = "preference"

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

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

PermissionHandler

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

typealias PermissionCheckCompletion = (CheckPermissionResult) -> Unit


object PermissionHandler {

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

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

Implementation

PermissionHandler.checkPermission(activity,
                    Manifest.permission.CAMERA) { result ->
                when (result) {
                    CheckPermissionResult.PermissionGranted -> {
                        // openCamera()
                    }
                    CheckPermissionResult.PermissionDisabled -> {
                        // displayAlert(noPermissionAlert)
                    }
                    CheckPermissionResult.PermissionAsk -> {
                        // requestCameraPermissions()
                    }
                    CheckPermissionResult.PermissionPreviouslyDenied -> {
                        // displayAlert(permissionRequestAlert)
                    }
                }
            }

Don't pass a Context in PermissionHandler.checkPermission() and then cast it as an Activity later on. A Context is not always an Activity
T
Thiago Saraiva

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

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

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

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

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

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

D
Dr. aNdRO

We can do it by this way?

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

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

@PermissionStatus
public static int getPermissionStatus(Activity activity, String permission) {
    if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
        return DENIED;
    } else {
        if (ActivityCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED) {
            return GRANTED;
        } else {
            return NEVER;
        }
    }
}

Unfortunately, this code does not distinguish between a situation where the permission was never requested before and where "never request again" was checked.
you should use the combination of this + the permission helper class to check if the permission is granted or not.
C
Community

May be useful for someone:--

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

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

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

Link for detailed working example.


it returns false for the very first time. not true
Yes, that's what I mentioned, if you check the flag in onRequestPermissionsResult() callback method, it will have two states only, specifically in this callback.
Unfortunately, shouldShowRequestPermissionRationale always returns false - regardless if the user ever denied the permission or not.
E
Eva
public void requestPermission(View view){
        if(ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
            if(ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,Manifest.permission.ACCESS_FINE_LOCATION)){

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

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

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

This is the openSettings method.

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

        Uri uri = Uri.fromParts("package",this.getPackageName(),null);
        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).setData(uri);
        startActivity(intent);
    }

I have created the intent so that the user will be redirected to the settings page if he had previously checked on the Don't show again button where he can give the permissions manually.
v
vinay shetty

Correct usage of shouldShowRequestPermissionRationale is the on onRequestPermissionsResult.

activity_main.xml

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

MainActivity.kt

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

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

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

        }

    }

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

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

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

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

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

        }

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

        }
        builder.show()
    }
}

E
Eric Aya

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

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

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

    setContentView(R.layout.activity_main);

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

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

}

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

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


private void result(){
          //your code
}

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


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

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