Разрешения Android M: запутано при использовании функции shouldShowRequestPermissionRationale ()

148

Я просматривал официальный документ о новой модели разрешений в Android M. В ней рассказывается о shouldShowRequestPermissionRationale()функции, которая возвращается, trueесли приложение запросило это разрешение ранее, а пользователь отклонил запрос. Если пользователь отклонил запрос на разрешение в прошлом и выбрал опцию «Больше не спрашивать», этот метод возвращается false.

Но как мы можем различить следующие два случая?

Случай 1 : приложение не имеет разрешения, и у пользователя ранее не запрашивалось разрешение. В этом случае shouldShowRequestPermissionRationale () вернет false, потому что это первый раз, когда мы спрашиваем пользователя.

Случай 2 : пользователь отказал в разрешении и выбрал «Больше не спрашивать», в этом случае также shouldShowRequestPermissionRationale () вернет false.

Я хотел бы отправить пользователя на страницу настроек приложения в случае 2. Как мне провести различие между этими двумя случаями?

akshayt23
источник
1
Принятый ответ хорош. В качестве альтернативы вы также можете использовать общий преф, чтобы узнать, запрашивало ли приложение разрешение ранее. Просто выбросить это на случай, если это будет более подходящим для чьей-либо ситуации.
Rockin4Life33
4
Существует также случай 3: пользователь запрашивал и предоставлял / отказывал в разрешении, но использовал настройки разрешений, чтобы вернуться к запросу «каждый раз». Тестирование показывает, что shouldShowRequestPermissionRationale()в этом случае возвращается false, что повредит любой код, использующий флаг «Я спрашивал раньше».
Логан Пикап
Вот пример Google, показывающий лучшие практики permissionsна Android. github.com/android/permissions-samples
itabdullah
@itabdullah Образец кода Google бесполезен, так как они даже не рассматривали весьма вероятный вариант использования «когда пользователь в прошлый раз лишал разрешения». : - / типично
Кто-то где-то

Ответы:

172

После M Preview 1, если диалоговое окно отображается впервые , флажок « Никогда не спрашивать снова» отсутствует .

Если пользователь отклоняет запрос на разрешение, во втором запросе разрешения будет установлен флажок Никогда не запрашивать снова .

Итак, логика должна быть такой:

  1. Просить разрешение:

    if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
    } else {
        //Do the stuff that requires permission...
    }
    
  2. Проверьте, было ли разрешение отклонено или предоставлено в onRequestPermissionsResult.

    Если ранее было отказано в разрешении, на этот раз в диалоговом окне разрешений появится флажок Никогда не спрашивать .

    Позвоните, shouldShowRequestPermissionRationaleчтобы узнать, проверил ли пользователь Никогда больше не спрашивать . shouldShowRequestPermissionRationaleМетод возвращает false, только если пользователь выбрал Никогда не спрашивать снова или политика устройства запрещает приложению иметь такое разрешение:

    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...
            }
        }
    }
    

Таким образом, вам не придется отслеживать, если пользователь установил флажок Никогда не спрашивать или нет.

ОТМ
источник
49
Следует пояснить, что следует, еслиShowRequestPermissionRationale () также вернет false, если у пользователя никогда не запрашивалось разрешение (т. Е. При первом запуске приложения). Вы не столкнетесь с этим случаем, если будете следовать логике приведенного примера. Но формулировка под 2 немного вводит в заблуждение.
Бен
15
Я не уверен, это кажется ошибочным. Как мы должны знать, если пользователь впервые получает запрос? Я должен отслеживать, если пользователя спросили, и если он сделал, то я должен полностью изменить логику. Не имеет никакого смысла для меня.
Даниэль Ф
4
Я думаю , что стоит отметить , что если вы передаете contextв ActivityCompat.shouldShowRequestPermissionRationale(...)параметре фактически типа Activity. Может не затронуть вас всех, но в моем случае это так.
aProperFox
7
Эта андроид логика такая чертовски глупая! Это вынуждает меня вызывать shouldфункцию обратного вызова и сохранять ее значение счетчика в NVM, чтобы узнать, нужно ли мне снова запрашивать запрос при следующем открытии приложения! ... вау (facepalm) ... было ли слишком сложно сделать только один вызов, возвращающий перечисление статуса ??
Shockwaver
2
Я думаю, что это большой провал со стороны Google. Официальные состояния документации , что shouldShowRequestPermissionRationale () должна вызываться перед проверкой прав доступа (см developer.android.com/training/permissions/requesting#explain ), но все ответы в StackOverflow называют его в onRequestPermissionResult () для того , чтобы отличить ли Пользователь нажал «Никогда не спрашивать» или нет.
Милош Черниловский
22

У меня была такая же проблема, и я понял это. Чтобы упростить жизнь, я написал класс util для обработки разрешений во время выполнения.

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

И методы PreferenceUtil заключаются в следующем.

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

Теперь все, что вам нужно, это использовать метод checkPermission с правильными аргументами.

Вот пример,

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

Случай 1: приложение не имеет разрешения, и у пользователя ранее не запрашивалось разрешение. В этом случае shouldShowRequestPermissionRationale () вернет false, потому что это первый раз, когда мы спрашиваем пользователя.

Случай 2: пользователь отказал в разрешении и выбрал «Больше не спрашивать», в этом случае также shouldShowRequestPermissionRationale () вернет false.

Я хотел бы отправить пользователя на страницу настроек приложения в случае 2. Как мне провести различие между этими двумя случаями?

Вы получите обратный вызов onPermissionAsk для случая 1 и onPermissionDisabled для случая 2.

Удачного кодирования :)

muthuraj
источник
Отличное объяснение, брат. Следовал вашей точно такой же процедуре. :)
Sumit Jha
Что я заполняю для этого действия? public void onPermissionAsk() { ActivityCompat.requestPermissions( thisActivity, ... ,
Mardymar
@Mardymar thisActivityне что иное, как YourActivity.this.
Мутурадж
1
как обрабатывать несколько разрешений и как интегрировать этот код внутри фрагмента.
Таймур
Какого рода contextвы используете? shouldShowRequestPermissionRationale(permission)не существует android.content.Context. это в ActivityCompat
Hilikus
9

ОБНОВИТЬ

Я считаю, что ответ CanC ниже является правильным, которому следует следовать. Единственный способ узнать наверняка - это проверить это в обратном вызове onRequestPermissionResult с помощью shouldShowPermissionRationale.

==

Мой оригинальный ответ:

Единственный способ, который я нашел, - это самостоятельно отслеживать, является ли это в первый раз или нет (например, с помощью общих настроек). Если это не первый раз, используйте shouldShowRequestPermissionRationale()дифференциацию.

Также см .: Android M - проверка прав доступа во время выполнения - как определить, если пользователь установил флажок «Никогда не спрашивать»?

Алекс Флореску
источник
1
Да, даже я согласен с тем, что метод CanC - тот, которому следует следовать. Я собираюсь отметить это как принятый ответ.
akshayt23
6

Насколько я понимаю, shouldShowRequestPermissionRationale () запускает несколько вариантов использования под капотом и уведомляет приложение о том, показывать или нет объяснение запрашиваемых разрешений.

Идея, лежащая в основе разрешений времени выполнения, заключается в том, что большую часть времени пользователь будет отвечать «да» на запрос разрешения. Таким образом, пользователь должен будет сделать только один клик. Конечно, запрос должен быть использован в правильном контексте - то есть запрос разрешения камеры при нажатии кнопки «Камера».

Если пользователь отклоняет запрос, но через какое-то время приходит и снова нажимает кнопку «Камера», shouldShowRequestPermissionRationale () вернет true, поэтому приложение может показать некоторые содержательные объяснения, почему запрашивается разрешение, и почему приложение не будет работать правильно без него. Обычно в этом диалоговом окне отображается кнопка, чтобы снова отказать / принять решение позже, и кнопка для предоставления разрешений. Кнопка предоставления разрешений в диалоговом окне обоснования должна снова запустить запрос на разрешение. На этот раз у пользователя также будет установлен флажок «Никогда больше не показывать». Если он решит выбрать его и снова отклонить разрешение, он уведомит систему Android о том, что пользователь и приложение не на одной странице. Это действие будет иметь два последствия - shouldShowRequestPermissionRationale () всегда будет возвращать false,

Но есть и другой возможный сценарий, в котором можно использовать onRequestPermissionsResult. Например, некоторые устройства могут иметь политику устройств, которая отключает камеру (работает для CIA, DARPA и т. Д.). На этих устройствах onRequestPermissionsResult всегда будет возвращать false, а метод requestPermissions () будет молча отклонять запрос.

Это то, что я собрал, слушая подкаст с Беном Поесом - менеджером по продукту на платформе Android.
http://androidbackstage.blogspot.jp/2015/08/episode-33-permission-mission.html

Shumoapp
источник
6

Просто опубликуйте другой вариант, если кому-то захочется. Вы можете использовать EasyPermissions, предоставленную самим Google, чтобы, как сказано, «упростить системные разрешения Android M».

Тогда вам не придется обращаться shouldShowRequestPermissionRationaleнапрямую.

Вэй Ванг
источник
почему я не видел этот проект преждевременно :)
Влад
Проблема с EasyPermissions остается почти такой же. Просить permissionPermanentlyDeniedвнутренне только звонки shouldShowPermissionsRationaleи возвращается trueв том случае , если пользователь никогда не был предложен предоставить разрешение.
hgoebl
4

Если кто-то заинтересован в решении Kotlin, я реорганизовал ответ @muthuraj, чтобы быть в Kotlin. Также немного модернизировано, чтобы иметь блок завершения вместо слушателей.

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

Реализация

PermissionHandler.checkPermission(activity,
                    Manifest.permission.CAMERA) { result ->
                when (result) {
                    CheckPermissionResult.PermissionGranted -> {
                        // openCamera()
                    }
                    CheckPermissionResult.PermissionDisabled -> {
                        // displayAlert(noPermissionAlert)
                    }
                    CheckPermissionResult.PermissionAsk -> {
                        // requestCameraPermissions()
                    }
                    CheckPermissionResult.PermissionPreviouslyDenied -> {
                        // displayAlert(permissionRequestAlert)
                    }
                }
            }
bmjohns
источник
3

Проверьте эту реализацию. работает довольно хорошо для меня. в основном вы проверяете разрешения в методе checkPermissions (), передавая список разрешений. Вы проверяете результат запроса разрешения на onRequestPermissionsResult (). Реализация позволяет рассмотреть оба случая, когда пользователь выбирает «никогда больше не спрашивать» или нет. В этой реализации, если se выбирает «никогда больше не спрашивать», в диалоговом окне есть опция, позволяющая перейти к действию «Настройки приложения».

Весь этот код внутри моего фрагмента. Я думал, что было бы лучше создать специализированный класс для этого, например, PermissionManager, но я не уверен в этом.

/**
     * 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);
    }
Тьяго Сарайва
источник
2

Может быть кому-то пригодится: -

Что я заметил, так это то, что если мы проверяем флаг shouldShowRequestPermissionRationale () в методе обратного вызова onRequestPermissionsResult (), он показывает только два состояния.

Состояние 1: -Возврат истины: - Каждый раз, когда пользователь нажимает кнопку «Запретить» (включая самый первый раз).

Состояние 2: - Возвращает false: - если пользователь выбирает «никогда не спрашивает».

Ссылка на подробный рабочий пример .

Никс
источник
6
он возвращает ложь в самый первый раз. не соответствует действительности
JoM
Да, это то, что я упомянул, если вы отметите флаг в методе обратного вызова onRequestPermissionsResult (), он будет иметь только два состояния, особенно в этом обратном вызове.
Никс
2
К сожалению, shouldShowRequestPermissionRationale всегда возвращает false - независимо от того, отказал ли пользователь в разрешении или нет.
Игорь Ганапольский
1

Мы можем сделать это таким образом?

@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;
        }
    }
}
Доктор АНДРО
источник
К сожалению, этот код не различает ситуацию, когда разрешение никогда не запрашивалось ранее, и когда проверялось «никогда не запрашивать снова».
Бен
Вы должны использовать комбинацию этого + класса помощника разрешения, чтобы проверить, предоставлено ли разрешение или нет.
Доктор Андро
0

shouldShowRequestPermissionRationale для СПЕЦИАЛЬНОГО разрешения всегда возвращайте значение ИСТИНА ТОЛЬКО после того, как пользователь отклонил его без флажка

Мы заинтересованы в ложном значении

Таким образом, есть 3 случая, потерянные с ложным значением:

1. Ранее такого действия не было, и теперь пользователь решает согласиться или отказаться.

Просто определите предпочтение, ASKED_PERMISSION_*которое не существует сейчас и было бы правдоподобно при onRequestPermissionsResultего запуске в любом случае согласиться или отрицать

Так что, хотя это предпочтение не существует, нет причин для проверкиshouldShowRequestPermissionRationale

2. пользователь нажал согласиться.

Просто сделайте:

checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED

Который вернет истину и нет причин проверятьshouldShowRequestPermissionRationale

3. пользователь нажал кнопку «Отклонить с флажком» (второй или более раз)

Это ВРЕМЯ для работы с shouldShowRequestPermissionRationaleкоторой будет возвращать FALSE

(предпочтение существует, и у нас нет разрешения)

Влад
источник
0

Этот код запрашивает у пользователя запросить разрешение во время выполнения, если пользователь разрешает, он выполняет метод результата, если пользователь отказывает, он запрашивает снова с описанием пользователя deny (он запрашивает снова с инструкциями), но если пользователь выбирает, никогда не спрашивать снова. он обрабатывает никогда не спрашивать, отображать опцию открытых настроек с инструкциями.

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;
                }
        }
}
Абхишек Гарг
источник