Как мы отличаемся от требований, которые не заданы в режиме ожидания?

Когда дело доходит до разрешений времени выполнения M Developer Preview, в соответствии с Google:

  • Если вы никогда не запрашивали определенное разрешение раньше, просто спросите его

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

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

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

Для запросов, запрашиваемых при первом запуске приложения, это не большая проблема. Существует множество рецептов для определения того, что это, вероятно, первый запуск вашего приложения (например, boolean значение в SharedPreferences), и поэтому вы предполагаете, что если это первый запуск вашего приложения, вы никогда не будете -заполненное состояние.

Тем не менее, часть видения разрешений во время выполнения - это то, что вы не можете просить их всех. Разрешения, привязанные к дополнительным функциям, о которых вы можете попросить позже, когда пользователь нажимает на то, что требует этого разрешения. Здесь приложение может быть запущено много раз в течение нескольких месяцев, прежде чем нам вдруг потребуется запросить другое разрешение.

В этих случаях мы должны отслеживать, просили ли мы сами разрешения? Или есть что-то в Android M API, который мне не хватает, что говорит нам, спрашивали мы раньше или нет?

Ответ 1

В соответствии с текущим примером: https://github.com/googlesamples/android-RuntimePermissions/blob/master/Application/src/main/java/com/example/android/system/runtimepermissions/MainActivity.java#L195

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
        int[] grantResults) {
    if (requestCode == REQUEST_CAMERA) {
        if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            doThing();
            //STORE FALSE IN SHAREDPREFERENCES
        } else {
            //STORE TRUE IN SHAREDPREFERENCES
        }
    }

Хранить логическое значение в SharedPreferences с ключом как ваш код и значение разрешения, как указано выше, чтобы указать, было ли ранее отказано в предпочтении.

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

Ответ 2

Я знаю, что я публикую очень поздно, но подробный пример может быть полезен для кого-то.

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

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

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

Вот пример с несколькими запросами на запрос: -

При запуске приложения требуется 2 разрешения. SEND_SMS и ACCESS_FINE_LOCATION (оба указаны в manifest.xml).

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

введите описание изображения здесь

public static final int REQUEST_ID_MULTIPLE_PERMISSIONS = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if(checkAndRequestPermissions()) {
        // carry on the normal flow, as the case of  permissions  granted.
    }
}

private  boolean checkAndRequestPermissions() {
    int permissionSendMessage = ContextCompat.checkSelfPermission(this,
            Manifest.permission.SEND_SMS);
    int locationPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
    List<String> listPermissionsNeeded = new ArrayList<>();
    if (locationPermission != PackageManager.PERMISSION_GRANTED) {
        listPermissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
    }
    if (permissionSendMessage != PackageManager.PERMISSION_GRANTED) {
        listPermissionsNeeded.add(Manifest.permission.SEND_SMS);
    }
    if (!listPermissionsNeeded.isEmpty()) {
        ActivityCompat.requestPermissions(this, listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]),REQUEST_ID_MULTIPLE_PERMISSIONS);
        return false;
    }
    return true;
}

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

Вы должны проверить значение параметра shouldShowRequestPermissionRationale() в методе обратного вызова onRequestPermissionsResult().

Есть только два случая: -

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

Случай 2:. Только если пользователь выбирает "никогда не спрашивает", он возвращает false. В этом случае мы можем продолжить с ограниченной функциональностью и помочь пользователю активировать разрешения от настроек для большей функциональности, или мы можем закончить настройку, если разрешения тривиальны для приложения.

CASE-1

Случай-1

CASE-2

Случай - 2

@Override
    public void onRequestPermissionsResult(int requestCode,
                                           String permissions[], int[] grantResults) {
        Log.d(TAG, "Permission callback called-------");
        switch (requestCode) {
            case REQUEST_ID_MULTIPLE_PERMISSIONS: {

                Map<String, Integer> perms = new HashMap<>();
                // Initialize the map with both permissions
                perms.put(Manifest.permission.SEND_SMS, PackageManager.PERMISSION_GRANTED);
                perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);
                // Fill with actual results from user
                if (grantResults.length > 0) {
                    for (int i = 0; i < permissions.length; i++)
                        perms.put(permissions[i], grantResults[i]);
                    // Check for both permissions
                    if (perms.get(Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_GRANTED
                            && perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
                        Log.d(TAG, "sms & location services permission granted");
                        // process the normal flow
                        //else any one or both the permissions are not granted
                    } else {
                            Log.d(TAG, "Some permissions are not granted ask again ");
                            //permission is denied (this is the first time, when "never ask again" is not checked) so ask again explaining the usage of permission
//                        // shouldShowRequestPermissionRationale will return true
                            //show the dialog or snackbar saying its necessary and try again otherwise proceed with setup.
                            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.SEND_SMS) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
                                showDialogOK("SMS and Location Services Permission 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.
                                                        break;
                                                }
                                            }
                                        });
                            }
                            //permission is denied (and never ask again is  checked)
                            //shouldShowRequestPermissionRationale will return false
                            else {
                                Toast.makeText(this, "Go to settings and enable permissions", Toast.LENGTH_LONG)
                                        .show();
    //                            //proceed with logic by disabling the related features or quit the app.
                            }
                    }
                }
            }
        }

    }

    private void showDialogOK(String message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(this)
                .setMessage(message)
                .setPositiveButton("OK", okListener)
                .setNegativeButton("Cancel", okListener)
                .create()
                .show();
    }

Ответ 3

Нет, вам не нужно отслеживать, запрашивали ли вы разрешение, и вам не нужно отличать Never-Asked от Stop-Asking.

Состояние 1 и 3 одинаково для разработчика приложений: вам нужно разрешение и ActivityCompat.checkSelfPermission != PackageManager.PERMISSION_GRANTED, тогда вы просто запрашиваете разрешение через ActivityCompat.requestPermissions(), когда пользователь вводит функцию, требующую разрешения, независимо от того, сколько времени, которое вы запросили. Пользователь в конечном итоге "Грант" или "Отклонить" его с помощью "Никогда не спрашивать снова". Дизайн НЕ запрещает вам многократно открывать диалоговое окно с запросом на запрос.

Однако дизайн поощряет вас объяснять цель разрешения в какой-то момент - ваше состояние 2. shouldShowRequestPermissionRationale() НЕ используется для определения того, следует ли запрашивать разрешение, оно использовалось для определения того, следует ли показывать объяснения, ПЕРЕД тем, как вы запрашиваете разрешение.

Еще несколько объяснений относительно состояния 3:

  • Да, мы должны перестать беспокоить пользователя, прекратив показывать объяснение, а не останавливать запрос. Вот почему они предоставили shouldShowRequestPermissionRationale().
  • Это не мешает сохранить запрос на получение разрешения. После того, как пользователь выбрал "никогда не спрашивать снова", ActivityCompat.requestPermissions() больше не будет отображать диалоговое окно.
  • Лучше отключить соответствующий пользовательский интерфейс каждый раз, когда мы узнаем, что у нас нет разрешения, во время сеанса с одним пользователем. Вместо отключения UI после shouldShowRequestPermissionRationale() вернуть false.

Ответ 4

У меня есть подход к решению вашей проблемы, мне кажется, это работает очень хорошо.

Я различаю, не спрашивая из-за остановки с помощью SharedPreferences, я приведу вам пример того, как я это использую.

private void requestAccountPermission() {

        SharedPreferences mPreferences = getSharedPreferences("configuration", MODE_PRIVATE);
        boolean firstTimeAccount = mPreferences.getBoolean("firstTimeAccount", true);

        if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.GET_ACCOUNTS)) {
            // 2. Asked before, and the user said "no"
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.GET_ACCOUNTS}, REQUEST_CODE_ACCOUNTS);
        }else {
            if(firstTimeAccount) { 
                // 1. first time, never asked 
                SharedPreferences.Editor editor = mPreferences.edit();
                editor.putBoolean("firstTimeAccount", false);
                editor.commit();

                // Account permission has not been granted, request it directly.
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.GET_ACCOUNTS},REQUEST_CODE_ACCOUNTS);
            }else{
                // 3. If you asked a couple of times before, and the user has said "no, and stop asking"

                // Your code
            }
        }
    }

Ответ 5

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

public boolean mPermissionRationaleDialogShown = false;

public void checkPermission() {
    if (ContextCompat.checkSelfPermission(this, "PermissionName")
            != PackageManager.PERMISSION_GRANTED) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName")) {
            showPermissionRequiredDialog();
        } else {
            askPermission();
        }
    } else {
       // Permission Granted
    }
}

public void askPermission() {
    ActivityCompat.requestPermissions(this,
            new String[]{"PermissionName"}, permissionRequestCode);
}

public void showPermissionRequiredDialog() {
    mPermissionRationaleDialogShown = true;
    // Dialog to show why permission is required
}

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == PERMISSION_REQUEST_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Permission Granted
        } else {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName")
                    && !mPermissionRationaleDialogShown) {
                // Permission dialog was shown for first time
            } else if (ActivityCompat.shouldShowRequestPermissionRationale(this, "PermissionName")
                    && mPermissionRationaleDialogShown){
                // User deny permission without Never ask again checked
            } else if (!ActivityCompat.shouldShowRequestPermissionRationale(this, PERMISSION_READ_EXTERNAL)
                    && mPermissionRationaleDialogShown) {
                // User has checked Never ask again during this permission request
            } else {
                // No permission dialog shown to user has user has previously checked Never ask again. Here we can show dialog to open setting screen to change permission
            }
        }
    } else {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

Ответ 6

После того, как вы попробуете все ответы здесь и некоторые другие сообщения через Интернет. Я узнал, что мне нужно использовать sharedPreference isLocationPermissionDialogShown (по умолчанию false), и каждая вещь работает как ожидалось.

  • Если первый раз просил разрешения. В этом случае shouldShowRequestPermissionRationale возвращает false и isLocationPermissionDialogShown также false
  • Второе время shouldShowRequestPermissionRationale return true и показывая диалог, мы устанавливаем isLocationPermissionDialogShown на true. и когда мы проверим условие, оба будут true
  • Каждое время, пока не будет просить снова shouldShowRequestPermissionRationale return true и isLocationPermissionDialogShown возвращает true
  • Если Never Ask Again отмечен галочкой shouldShowRequestPermissionRationale return false и isLocationPermissionDialogShown возвращает true. Это то, что нам нужно.

Пожалуйста, проверьте рабочий пример.

public class MainActivity extends AppCompatActivity {
    SharedPreferences sharedPreferences;
    String locationPermission;
    String prefLocationPermissionKey = "isLocationPermissionDialogShown";
    private final int PERMISSION_REQUEST_CODE_LOCATION = 1001;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        locationPermission = Manifest.permission.ACCESS_FINE_LOCATION;
        sharedPreferences = getSharedPreferences("configuration", MODE_PRIVATE);

        //check for android version
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //Check for permission
            if (checkSelfPermission(locationPermission) != PackageManager.PERMISSION_GRANTED) {
                //check if clarification dialog should be shown.
                if (shouldShowRequestPermissionRationale(locationPermission)) {
                    showClarificationDialog(locationPermission, PERMISSION_REQUEST_CODE_LOCATION);
                } else  {
                    requestPermissions(new String[] { locationPermission}, PERMISSION_REQUEST_CODE_LOCATION);
                }
            } else {
                Log.d("nets-debug", "permission already grranted");
            }
        }

    }

    @Override
    @TargetApi(Build.VERSION_CODES.M)
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

        if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED) {
            //for location permission
            if (requestCode == PERMISSION_REQUEST_CODE_LOCATION) {
                boolean isLocationPermissionDialogShown = sharedPreferences.getBoolean(prefLocationPermissionKey, false);

                if (!shouldShowRequestPermissionRationale(locationPermission) && isLocationPermissionDialogShown) {
                    // user selected Never Ask Again. do something
                    Log.d("nets-debug", "never ask again");
                } else {
                    // all other conditions like first time asked, previously denied etc are captured here and can be extended if required.
                    Log.d("nets-debug", "all other cases");
                }
            }

        }

    }

    @TargetApi(Build.VERSION_CODES.M)
    public void showClarificationDialog(final String permission, final int requestCode) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Permission Required");
        builder.setMessage("Please grant Location permission to use all features of this app");
        builder.setPositiveButton("Grant", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                SharedPreferences.Editor editor = sharedPreferences.edit();
                editor.putBoolean(prefLocationPermissionKey, true);
                editor.apply();
                requestPermissions(new String[] {permission}, requestCode);
            }
        });
        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Toast.makeText(getApplicationContext(), "This permission required", Toast.LENGTH_LONG).show();
            }
        });
        builder.create().show();
    }

}

Надеюсь, это поможет.

Ответ 7

Вы можете посмотреть here - есть блок-схема, которая объясняет процесс неплохо. Это также объясняет, когда вы должны вызывать shouldShowRequestPermissionRationale() и когда он возвращает true.

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

Ответ 8

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

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

    public static final int GRANTED = 0;
    public static final int DENIED = 1;
    public static final int BLOCKED = 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;
            }
            return DENIED;
        }
        return GRANTED;
    }

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

Я также использовал это здесь.