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


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

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

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

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

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

11 111

11 ответов:

после 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...
            }
        }
    }
    

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

у меня была такая же проблема и я все понял. Чтобы сделать жизнь намного проще, я написал класс 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.

удачи в кодировании :)

обновление

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

==

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

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

Смотрите также: Android M-проверьте разрешение на выполнение-как определить, если пользователь проверил "никогда не спрашивайте снова"?

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

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

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

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

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

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

тогда вам не нужно обрабатывать shouldShowRequestPermissionRationale напрямую.

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

может быть полезно для кого-то:--

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

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

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

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

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

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

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

мы заинтересованы в ложные стоимостью

и 3 случаи, потерянные с ложные значение:

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

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

так что пока это предпочтение не существует есть незачем проверить shouldShowRequestPermissionRationale

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

просто:

checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED

что вернет правда и незачем проверить shouldShowRequestPermissionRationale

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

это ВРЕМЕНИ на работу с shouldShowRequestPermissionRationale что вернет ложные

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

этот код запрашивает у пользователя разрешение во время выполнения, если пользователь позволяет, он выполняет метод результата, если пользователь отрицает, он снова спрашивает с описанием с пользователем 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;
                }
        }
}

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

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