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

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

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

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

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

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

11 ответов


после просмотра M 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 (), он показывает только два состояния.

состояние 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 только после того, как пользователь отклонил его без флажка

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

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

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

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

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

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

просто:

checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED

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

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

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

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


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

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