Android M-check runtime permission-как определить, если пользователь проверил "никогда не спрашивайте снова"?


согласно этому:http://developer.android.com/preview/features/runtime-permissions.html#coding приложение может проверить наличие разрешений на выполнение и запросить разрешения, если они еще не были предоставлены. После этого появится следующее диалоговое окно:

в случае, если пользователь отклоняет важное разрешение, imo приложение должно отображать объяснение, почему разрешение необходимо и какое влияние имеет снижение. Этот диалог имеет два опции:

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

если пользователь проверяет Never ask again однако второй диалог с объяснением не должен отображаться, особенно если пользователь уже отказался один раз раньше. Теперь вопрос: как мое приложение знает, проверил ли пользователь Never ask again? ИМО onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) не дает мне эту информацию.

второй вопрос be: есть ли у Google планы включить пользовательское сообщение в диалог разрешения, которое объяснило бы, почему приложение нуждается в разрешении? Таким образом, никогда не будет второго диалога, который, безусловно, сделает лучший ux.

21 242

21 ответ:

Developer Preview 2 вносит некоторые изменения в то, как разрешения запрашиваются приложением (см. Также http://developer.android.com/preview/support.html#preview2-notes).

первый диалог теперь выглядит так:

enter image description here

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

enter image description here

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

enter image description here

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

if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) {...

вот что говорит документация Android (https://developer.android.com/training/permissions/requesting.html):

чтобы помочь найти ситуации, когда вам нужно предоставить дополнительные объяснение, система обеспечивает Деятельность.shouldShowRequestPermissionRationale (String) метод. Этот метод возвращает значение true, если приложение запросило разрешение ранее и пользователь отказал в запросе. Это указывает на то, что вы вероятно, следует объяснить пользователю, зачем вам нужно разрешение.

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

чтобы узнать, если пользователь отказался с "никогда не спрашивайте снова" вы можете проверить еще раз shouldShowRequestPermissionRationale метод в вашем onRequestPermissionsResult, когда пользователь не предоставил разрешение.

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == REQUEST_PERMISSION) {
        // for each permission check if the user granted/denied them
        // you may want to group the rationale in a single dialog,
        // this is just an example
        for (int i = 0, len = permissions.length; i < len; i++) {
            String permission = permissions[i];
            if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
            // user rejected the permission
                boolean showRationale = shouldShowRequestPermissionRationale( permission );
                if (! showRationale) {
                    // user also CHECKED "never ask again"
                    // you can either enable some fall back,
                    // disable features of your app
                    // or open another dialog explaining
                    // again the permission and directing to
                    // the app setting
                } else if (Manifest.permission.WRITE_CONTACTS.equals(permission)) {
                    showRationale(permission, R.string.permission_denied_contacts);
                    // user did NOT check "never ask again"
                    // this is a good place to explain the user
                    // why you need the permission and ask if he wants
                    // to accept it (the rationale)
                } else if ( /* possibly check more permissions...*/ ) {
                }
            }
        }
    }
}

вы можете открыть Настройки Приложения с помощью этого кода:

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, REQUEST_PERMISSION_SETTING);

нет способа отправить пользователя непосредственно на авторизацию страница.

вы можете проверить shouldShowRequestPermissionRationale() в своем onRequestPermissionsResult().

shouldShowRequestPermissionRationale https://youtu.be/C8lUdPVSzDk?t=2m23s

проверить, есть ли разрешение или нет onRequestPermissionsResult(). Если не затем проверить shouldShowRequestPermissionRationale().

  1. если этот метод возвращает true затем покажите объяснение, почему это конкретное разрешение необходимо. Затем в зависимости от выбора пользователя снова requestPermissions().
  2. если он возвращает false затем показать сообщение об ошибке, что разрешение не было предоставлено и приложение не может продолжить работу или определенная функция отключена.

ниже приведен пример кода.

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    switch (requestCode) {
        case STORAGE_PERMISSION_REQUEST:
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // permission was granted :)
                downloadFile();
            } else {
                // permission was not granted
                if (getActivity() == null) {
                    return;
                }
                if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    showStoragePermissionRationale();
                } else {
                    Snackbar snackbar = Snackbar.make(getView(), getResources().getString(R.string.message_no_storage_permission_snackbar), Snackbar.LENGTH_LONG);
                    snackbar.setAction(getResources().getString(R.string.settings), new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            if (getActivity() == null) {
                                return;
                            }
                            Intent intent = new Intent();
                            intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                            Uri uri = Uri.fromParts("package", getActivity().getPackageName(), null);
                            intent.setData(uri);
                            OrderDetailFragment.this.startActivity(intent);
                        }
                    });
                    snackbar.show();
                }
            }
            break;
    }
}

по-видимому, google maps делает именно это для разрешения местоположения.

вот хороший и простой способ проверить текущий статус разрешения:

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

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

    @PermissionStatus 
    public static int getPermissionStatus(Activity activity, String androidPermissionName) {
        if(ContextCompat.checkSelfPermission(activity, androidPermissionName) != PackageManager.PERMISSION_GRANTED) {
            if(!ActivityCompat.shouldShowRequestPermissionRationale(activity, androidPermissionName)){
                return BLOCKED_OR_NEVER_ASKED;
            }
            return DENIED;
        }
        return GRANTED;
    }

предостережение: возвращает BLOCKED_OR_NEVER_ASKED первый запуск приложения, прежде чем пользователь принял / отказал в разрешении через приглашение пользователя (на устройствах sdk 23+)

обновление:

библиотека поддержки Android теперь также, похоже, имеет очень похожий класс android.support.v4.content.PermissionChecker, которая содержит checkSelfPermission() возвращает:

public static final int PERMISSION_GRANTED = 0;
public static final int PERMISSION_DENIED = -1;
public static final int PERMISSION_DENIED_APP_OP = -2;

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

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

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

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

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

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

Моя полная реализация будет как ниже. Это работает для обоих один или несколько просит разрешения. Используйте следующее или сразу используйте мое библиотека.

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if(permissions.length == 0){
        return;
    }
    boolean allPermissionsGranted = true;
    if(grantResults.length>0){
        for(int grantResult: grantResults){
            if(grantResult != PackageManager.PERMISSION_GRANTED){
                allPermissionsGranted = false;
                break;
            }
        }
    }
    if(!allPermissionsGranted){
        boolean somePermissionsForeverDenied = false;
        for(String permission: permissions){
            if(ActivityCompat.shouldShowRequestPermissionRationale(this, permission)){
                //denied
                Log.e("denied", permission);
            }else{
                if(ActivityCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED){
                    //allowed
                    Log.e("allowed", permission);
                } else{
                    //set to never ask again
                    Log.e("set to never ask again", permission);
                    somePermissionsForeverDenied = true;
                }
            }
        }
        if(somePermissionsForeverDenied){
            final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
            alertDialogBuilder.setTitle("Permissions Required")
                    .setMessage("You have forcefully denied some of the required permissions " +
                            "for this action. Please open settings, go to permissions and allow them.")
                    .setPositiveButton("Settings", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                                    Uri.fromParts("package", getPackageName(), null));
                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            startActivity(intent);
                        }
                    })
                    .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                        }
                    })
                    .setCancelable(false)
                    .create()
                    .show();
        }
    } else {
        switch (requestCode) {
            //act according to the request code used while requesting the permission(s).
        }
    }
}

если вы хотите обнаружить все "состояния" (первый раз отказано, просто было отказано, просто было отказано с "никогда не спрашивайте снова" или постоянно отказано), вы можете сделать следующее:

создать 2 булевых

private boolean beforeClickPermissionRat;
private boolean afterClickPermissionRat;

установите первый перед запросом разрешения:

beforeClickPermissionRat = shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE);

установите второй внутри вашего метода onRequestPermissionsResult:

afterClickPermissionRat = shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE);

используйте следующую "таблицу", чтобы сделать все, что вам нужно в onRequestPermissionsResult () (после проверив, что у вас все еще нет разрешения):

// before after
// FALSE  FALSE  =  Was denied permanently, still denied permanently --> App Settings
// FALSE  TRUE   =  First time deny, not denied permanently yet --> Nothing
// TRUE   FALSE  =  Just been permanently denied --> Changing my caption to "Go to app settings to edit permissions"
// TRUE   TRUE   =  Wasn't denied permanently, still not denied permanently --> Nothing

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

как мое приложение узнает, проверил ли пользователь "никогда не спрашивайте опять"?

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

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

после того, как пользователь отметил "Не спрашивайте снова", вопрос не может быть отображен снова. Но это может быть объяснено пользователю, что он ранее отказал в разрешении и должен предоставить разрешение в настройках. И ссылка на него в настройках, со следующим кодом:

@Override
public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults) {

    if (grantResults.length > 0
            && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        // now, you have permission go ahead
        // TODO: something

    } else {

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                Manifest.permission.READ_CALL_LOG)) {
            // now, user has denied permission (but not permanently!)

        } else {

            // now, user has denied permission permanently!

            Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), "You have previously declined this permission.\n" +
                "You must approve this permission in \"Permissions\" in the app settings on your device.", Snackbar.LENGTH_LONG).setAction("Settings", new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + BuildConfig.APPLICATION_ID)));

            }
        });
        View snackbarView = snackbar.getView();
        TextView textView = (TextView) snackbarView.findViewById(android.support.design.R.id.snackbar_text);
        textView.setMaxLines(5);  //Or as much as you need
        snackbar.show();

        }

    }
    return;
}

полное объяснение для каждого случая разрешения

/**
 *    Case 1: User doesn't have permission
 *    Case 2: User has permission
 *
 *    Case 3: User has never seen the permission Dialog
 *    Case 4: User has denied permission once but he din't clicked on "Never Show again" check box
 *    Case 5: User denied the permission and also clicked on the "Never Show again" check box.
 *    Case 6: User has allowed the permission
 *
 */
public void handlePermission() {
    if (ContextCompat.checkSelfPermission(MainActivity.this,
            Manifest.permission.WRITE_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED) {
        // This is Case 1. Now we need to check further if permission was shown before or not

        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)) {

            // This is Case 4.
        } else {
            // This is Case 3. Request for permission here
        }

    } else {
        // This is Case 2. You have permission now you can do anything related to it
    }
}

public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        // This is Case 2 (Permission is now granted)
    } else {
        // This is Case 1 again as Permission is not granted by user

        //Now further we check if used denied permanently or not
        if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
            // case 4 User has denied permission but not permanently

        } else {
            // case 5. Permission denied permanently.
            // You can open Permission setting's page from here now.
        }

    }
}

Я написал стенографию для запроса разрешения в Android M. Этот код также обрабатывает обратную совместимость со старыми версиями Android.

весь уродливый код извлекается В фрагмент, который присоединяется и отсоединяется к действию, запрашивающему разрешения.Вы можете использовать PermissionRequestManager следующим образом:

new PermissionRequestManager()
        // We need a AppCompatActivity here, if you are not using support libraries you will have to slightly change 
        // the PermissionReuqestManager class
        .withActivity(this)

        // List all permissions you need
        .withPermissions(android.Manifest.permission.CALL_PHONE, android.Manifest.permission.READ_CALENDAR)

        // This Runnable is called whenever the request was successfull
        .withSuccessHandler(new Runnable() {
            @Override
            public void run() {
                // Do something with your permissions!
                // This is called after the user has granted all 
                // permissions, we are one a older platform where 
                // the user does not need to grant permissions 
                // manually, or all permissions are already granted

            }
        })

        // Optional, called when the user did not grant all permissions
        .withFailureHandler(new Runnable() {
            @Override
            public void run() {
                // This is called if the user has rejected one or all of the requested permissions
                L.e(this.getClass().getSimpleName(), "Unable to request permission");

            }
        })

        // After calling this, the user is prompted to grant the rights
        .request();

взгляните:https://gist.github.com/crysxd/385b57d74045a8bd67c4110c34ab74aa

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

Как поясняется в запрос разрешений во время выполнения, этот метод возвращает true, если опция "никогда не спрашивайте снова" видно, false в противном случае; поэтому он возвращает false при первом отображении диалога, а затем со второго раза он возвращает true, и только если пользователь отрицает разрешение, выбрав опцию, в этот момент он снова возвращает false.

чтобы обнаружить такой случай, либо вы можете обнаружить последовательность false-true-false, либо (более простой) у вас может быть флаг, который отслеживает начальное время отображения диалогового окна. После этого, этот метод возвращает значение true или false, где false позволит вам определить, когда выбран параметр.

попробуйте эту простую библиотеку разрешение. Он будет обрабатывать все операции, связанные с разрешением в 3 простых шага. Это сэкономило мне время. вы можете закончить все разрешения, связанные с работой в 15 минут.

Он может обрабатывать отказ, он может обрабатывать никогда не спрашивать снова, он может вызывать настройки приложения для разрешения, он может дать рациональное сообщение, он может дать сообщение об отказе, он может дать список принятых разрешений, он может дать список запрещенных разрешений и так далее.

https://github.com/ParkSangGwon/TedPermission

Шаг 1: добавить вашу зависимость

dependencies {
     compile 'gun0912.ted:tedpermission:2.1.1'
     //check the above link for latest libraries
}

Шаг 2: задать разрешения

TedPermission.with(this)
    .setPermissionListener(permissionlistener)
    .setDeniedMessage("If you reject permission,you can not use this service\n\nPlease turn on permissions at [Setting] > [Permission]")
    .setPermissions(Manifest.permission.READ_CONTACTS, Manifest.permission.ACCESS_FINE_LOCATION)
    .check();

Шаг 3: обрабатывать ответ разрешения

PermissionListener permissionlistener = new PermissionListener() {
    @Override
    public void onPermissionGranted() {
        Toast.makeText(MainActivity.this, "Permission Granted", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onPermissionDenied(ArrayList<String> deniedPermissions) {
        Toast.makeText(MainActivity.this, "Permission Denied\n" + deniedPermissions.toString(), Toast.LENGTH_SHORT).show();
    }
};

пожалуйста, не бросайте в меня камни за это решение.

это работает, но немного "суховато".

когда вы называете requestPermissions, зарегистрируйте текущее время.

        mAskedPermissionTime = System.currentTimeMillis();

затем в onRequestPermissionsResult

если результат не получен, проверьте время еще раз.

 if (System.currentTimeMillis() - mAskedPermissionTime < 100)

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

использовать по своему усмотрению риски.

можно использовать

shouldShowRequestPermissionRationale()

внутри

onRequestPermissionsResult()

Смотрите пример ниже:

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

@Override
public void onClick(View v) {
    if (v.getId() == R.id.appCompatBtn_changeProfileCoverPhoto) {
        if (Build.VERSION.SDK_INT < 23) { // API < 23 don't need to ask permission
            navigateTo(MainActivity.class); // Navigate to activity to change photos
        } else {
            if (ContextCompat.checkSelfPermission(SettingsActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    != PackageManager.PERMISSION_GRANTED) {
                // Permission is not granted yet. Ask for permission...
                requestWriteExternalPermission();
            } else {
                // Permission is already granted, good to go :)
                navigateTo(MainActivity.class);
            }
        } 
    }
}

когда пользователь ответит на диалоговое окно разрешения, мы перейдем к onRequestPermissionResult:

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    if (requestCode == WRITE_EXTERNAL_PERMISSION_REQUEST_CODE) {
        // Case 1. Permission is granted.  
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {  
            if (ContextCompat.checkSelfPermission(SettingsActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                    == PackageManager.PERMISSION_GRANTED) {
                // Before navigating, I still check one more time the permission for good practice.
                navigateTo(MainActivity.class);
            }
        } else { // Case 2. Permission was refused
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                // Case 2.1. shouldShowRequest... returns true because the
                // permission was denied before. If it is the first time the app is running we will 
                // end up in this part of the code. Because he need to deny at least once to get 
                // to onRequestPermissionsResult. 
                Snackbar snackbar = Snackbar.make(findViewById(R.id.relLayout_container), R.string.you_must_verify_permissions_to_send_media, Snackbar.LENGTH_LONG);
                snackbar.setAction("VERIFY", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        ActivityCompat.requestPermissions(SettingsActivity.this
                                , new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}
                                , WRITE_EXTERNAL_PERMISSION_REQUEST_CODE);
                    }
                });
                snackbar.show();
            } else {
                // Case 2.2. Permission was already denied and the user checked "Never ask again". 
                // Navigate user to settings if he choose to allow this time.
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setMessage(R.string.instructions_to_turn_on_storage_permission)
                        .setPositiveButton(getString(R.string.settings), new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                Intent settingsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                Uri uri = Uri.fromParts("package", getPackageName(), null);
                                settingsIntent.setData(uri);
                                startActivityForResult(settingsIntent, 7);
                            }
                        })
                        .setNegativeButton(getString(R.string.not_now), null);
                Dialog dialog = builder.create();
                dialog.show();
            }
        }
    }

}

полезная функция для определения, если произвольное разрешение было заблокировано от запроса (в Котлине):

private fun isPermissionBlockedFromAsking(activity: Activity, permission: String): Boolean {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        return ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED
            && !activity.shouldShowRequestPermissionRationale(permission)
            && PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(permission, false)
    }
    return false
}

использование этого требует установки общего предпочтения boolean с именем желаемого разрешения (например,android.Manifest.permission.READ_PHONE_STATE) в true при первом запросе разрешения.


объяснение:

Build.VERSION.SDK_INT >= Build.VERSION_CODES.M так как часть кода может быть запущена только на уровне API 23+.

ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED чтобы проверить, что у нас еще нет разрешение.

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

PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(permission, false) это используется (наряду с установкой значения true при первом запросе разрешения), чтобы различать состояния "никогда не спрашивал" и "никогда не спрашивал снова", поскольку предыдущая строка не возвращает эту информацию.

Я также хотел бы получить информацию о том, выбрал ли пользователь "никогда не спрашивайте снова". Я достиг "почти решения" с уродливым флагом, но прежде чем я расскажу вам, как, я расскажу вам о моей мотивации:

Я хотел бы предложить разрешение, ссылающееся на функциональность изначально. Если пользователь использует его и не имеет прав, он / она получает либо 1-й диалог сверху, либо оба 2-го и 3-го. Когда пользователь выбрал "никогда не спрашивайте снова", я хотел бы отключите функциональность и отобразить его по-другому. - Мое действие инициируется текстовой записью spinner, я также хотел бы добавить "(разрешение отменено) " к отображаемому тексту метки. Это показывает пользователю: "есть функциональность, но я не могу ее использовать из-за моих настроек разрешений."Однако это кажется невозможным, поскольку я не могу проверить, было ли выбрано "никогда не спрашивайте снова".

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

вместо этого вы получите обратный вызов на onRequestPermissionsResult() Как PERMISSION_DENIED, когда вы снова запрашиваете разрешение, попадая в ложное состояние shouldShowRequestPermissionRationale()

из Android doc:

когда система просит пользователя предоставить разрешение, пользователь имеет возможность сообщить системе, чтобы она не запрашивала это разрешение снова. В этом случае, в любое время приложение использует requestPermissions() чтобы снова запросить это разрешение, система немедленно отклоняет запрос. Система вызывает ваш onRequestPermissionsResult() метод обратного вызова и передает PERMISSION_DENIED, так же, как если бы пользователь явно вновь отклонил ваш запрос. Это означает, что при вызове requestPermissions(), вы не можете предположить, что любое прямое взаимодействие с пользователем происходит.

Я должен реализовать динамическое разрешение для камеры. Где происходит 3 возможных случая: 1. Позвольте, 2. Отказано, 3. Не спрашивай больше.

 @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

    for (String permission : permissions) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), permission)) {
            //denied
            Log.e("denied", permission);
        } else {
            if (ActivityCompat.checkSelfPermission(getActivity(), permission) == PackageManager.PERMISSION_GRANTED) {
                //allowed
                Log.e("allowed", permission);
            } else {
                //set to never ask again
                Log.e("set to never ask again", permission);
                //do something here.
            }
        }
    }
    if (requestCode != MaterialBarcodeScanner.RC_HANDLE_CAMERA_PERM) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        return;
    }
    if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        mScannerView.setResultHandler(this);
        mScannerView.startCamera(mCameraId);
        mScannerView.setFlash(mFlash);
        mScannerView.setAutoFocus(mAutoFocus);
        return;
    } else {
        //set to never ask again
        Log.e("set to never ask again", permissions[0]);
    }
    DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            dialog.cancel();
        }
    };
    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    builder.setTitle("Error")
            .setMessage(R.string.no_camera_permission)
            .setPositiveButton(android.R.string.ok, listener)
            .show();


}

private void insertDummyContactWrapper() {
        int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.CAMERA);
        if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.CAMERA},
                    REQUEST_CODE_ASK_PERMISSIONS);
            return;
        }
        mScannerView.setResultHandler(this);
        mScannerView.startCamera(mCameraId);
        mScannerView.setFlash(mFlash);
        mScannerView.setAutoFocus(mAutoFocus);
    }

private int checkSelfPermission(String camera) {
    if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
        return REQUEST_CODE_ASK_PERMISSIONS;
    } else {
        return REQUEST_NOT_CODE_ASK_PERMISSIONS;
    }
}

можно использовать if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA) метод, чтобы определить, является ли никогда не спрашивать проверяется или нет.

для получения дополнительной информации:проверить это

чтобы проверить наличие нескольких разрешений используйте:

  if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)
                                || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                                || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)
                                || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
                            showDialogOK("Service Permissions are required for this app",
                                    new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            switch (which) {
                                                case DialogInterface.BUTTON_POSITIVE:
                                                    checkAndRequestPermissions();
                                                    break;
                                                case DialogInterface.BUTTON_NEGATIVE:
                                                    // proceed with logic by disabling the related features or quit the app.
                                                    finish();
                                                    break;
                                            }
                                        }
                                    });
                        }
                        //permission is denied (and never ask again is  checked)
                        //shouldShowRequestPermissionRationale will return false
                        else {
                            explain("You need to give some mandatory permissions to continue. Do you want to go to app settings?");
                            //                            //proceed with logic by disabling the related features or quit the app.
                        }

объяснить() метод

private void explain(String msg){
        final android.support.v7.app.AlertDialog.Builder dialog = new android.support.v7.app.AlertDialog.Builder(this);
        dialog.setMessage(msg)
                .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface paramDialogInterface, int paramInt) {
                        //  permissionsclass.requestPermission(type,code);
                        startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:com.exampledemo.parsaniahardik.marshmallowpermission")));
                    }
                })
                .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface paramDialogInterface, int paramInt) {
                        finish();
                    }
                });
        dialog.show();
    }

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

расширения mVckответ выше, следующая логика определяет, был ли" никогда не спрашивайте снова " проверен для данного запроса разрешения:

bool bStorage = grantResults[0] == Permission.Granted;
bool bNeverAskForStorage =
    !bStorage && (
        _bStorageRationaleBefore == true  && _bStorageRationaleAfter == false ||
        _bStorageRationaleBefore == false && _bStorageRationaleAfter == false
    );

который приведен ниже (для полного примера см. Это ответ)

private bool _bStorageRationaleBefore;
private bool _bStorageRationaleAfter;        
private const int ANDROID_PERMISSION_REQUEST_CODE__SDCARD = 2;
//private const int ANDROID_PERMISSION_REQUEST_CODE__CAMERA = 1;
private const int ANDROID_PERMISSION_REQUEST_CODE__NONE = 0;

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
{
    base.OnRequestPermissionsResult(requestCode, permissions, grantResults);

    switch (requestCode)
    {
        case ANDROID_PERMISSION_REQUEST_CODE__SDCARD:               
            _bStorageRationaleAfter = ShouldShowRequestPermissionRationale(Android.Manifest.Permission.WriteExternalStorage);
            bool bStorage = grantResults[0] == Permission.Granted;
            bool bNeverAskForStorage =
                !bStorage && (
                    _bStorageRationaleBefore == true  && _bStorageRationaleAfter == false ||
                    _bStorageRationaleBefore == false && _bStorageRationaleAfter == false
                );      
            break;                
    }
}

private List<string> GetRequiredPermissions(out int requestCode)
{
    // Android v6 requires explicit permission granting from user at runtime for security reasons            
    requestCode = ANDROID_PERMISSION_REQUEST_CODE__NONE; // 0
    List<string> requiredPermissions = new List<string>();

    _bStorageRationaleBefore = ShouldShowRequestPermissionRationale(Android.Manifest.Permission.WriteExternalStorage);
    Permission writeExternalStoragePerm = ApplicationContext.CheckSelfPermission(Android.Manifest.Permission.WriteExternalStorage);
    //if(extStoragePerm == Permission.Denied)
    if (writeExternalStoragePerm != Permission.Granted)
    {
        requestCode |= ANDROID_PERMISSION_REQUEST_CODE__SDCARD;
        requiredPermissions.Add(Android.Manifest.Permission.WriteExternalStorage);
    }

    return requiredPermissions;
}

protected override void OnCreate(Bundle savedInstanceState)
{
    base.OnCreate(savedInstanceState);

        // Android v6 requires explicit permission granting from user at runtime for security reasons
        int requestCode;
        List<string> requiredPermissions = GetRequiredPermissions(out requestCode);
        if (requiredPermissions != null && requiredPermissions.Count > 0)
        {
            if (requestCode >= ANDROID_PERMISSION_REQUEST_CODE__SDCARD)                    
            {
                _savedInstanceState = savedInstanceState;
                RequestPermissions(requiredPermissions.ToArray(), requestCode);
                return;
            }
        }
    }            

    OnCreate2(savedInstanceState);
}
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
    switch (requestCode) {
        case PERMISSIONS_REQUEST_EXTERNAL_STORAGE: {
            if (grantResults.length > 0) {
                if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                    // Denied
                } else {
                    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                        // To what you want
                    } else {
                       // Bob never checked click
                    }
                }
            }
        }
    }
}