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

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

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

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

Случай 2: пользователь отказал в разрешении и выбрал "Не спрашивать еще раз", в этом случае тоже должноShowRequestPermissionRationale() вернет false.

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

Ответ 1

После M Preview 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...
    }
    
  • Убедитесь, что разрешение было отклонено или предоставлено в 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...
            }
        }
    }
    

Итак, вам не нужно отслеживать, проверен ли пользователь. Никогда не спрашивайте снова или нет.

Ответ 2

У меня была такая же проблема, и я понял это. Чтобы сделать жизнь намного проще, я написал класс 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 (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: Пользователь отказал в разрешении и выбрал "Не спрашивайте снова", в этом случае тоже должноShowRequestPermissionRationale() будет return false.

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

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

Счастливое кодирование:)

Ответ 3

ОБНОВЛЕНИЕ

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

==

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

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

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

Ответ 4

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

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

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

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

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

Ответ 5

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

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

Ответ 6

Проверьте эту реализацию. работает очень хорошо для меня. в основном вы проверяете разрешения в методе 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 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);
    }

Ответ 7

Может быть полезно кому-то: -

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

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

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

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

Ответ 8

shouldShowRequestPermissionRationale для разрешения SPECIAL всегда возвращает TRUE ONLY после того, как пользователь отказался от него без флажка

нас интересует FALSE value

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

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

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

Итак, пока этого предпочтения не существует, существует без причины для проверки shouldShowRequestPermissionRationale

2. пользователь нажал кнопку "согласен".

Просто выполните:

checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED

Что вернет истина, а без причины проверить shouldShowRequestPermissionRationale

3. пользователь щелкнул опцию deny с помощью флажка (задано второе или большее время)

Он ВРЕМЯ работает с shouldShowRequestPermissionRationale, который вернет FALSE

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