Android M - проверить разрешение во время выполнения - как определить, проверял ли пользователь "Никогда больше не спрашивать"?
В соответствии с этим: http://developer.android.com/preview/features/runtime-permissions.html приложение может проверять разрешения во время выполнения и запрашивать разрешения, если оно еще не было предоставлено. Затем появится следующий диалог:
В случае, если пользователь отклоняет важное разрешение, в приложении должно отображаться объяснение, почему требуется разрешение и какое влияние имеет снижение. Этот диалог имеет две опции:
- повторите попытку (разрешение запрашивается снова)
- отрицать (приложение будет работать без этого разрешения).
Если пользователь проверяет Never ask again
однако второе диалоговое окно с объяснением не должно отображаться, особенно если пользователь уже один раз отказался. Теперь вопрос: как мое приложение узнает, проверил ли пользователь Never ask again
? ИМО onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
не дает мне эту информацию.
Второй вопрос: есть ли у Google планы по добавлению в диалоговое окно разрешений специального сообщения, объясняющего, почему приложению нужны разрешения? Таким образом, никогда не будет второго диалога, который, несомненно, улучшит работу.
30 ответов
В Developer Preview 2 внесены некоторые изменения в том, как приложение запрашивает разрешения (см. Также http://developer.android.com/preview/support.html).
Первый диалог теперь выглядит так:
Нет флажка "Больше никогда не показывать" (в отличие от предварительного просмотра для разработчиков 1). Если пользователь отказывает в разрешении и если разрешение имеет важное значение для приложения, он может представить другой диалог, объясняющий причину, по которой приложение запрашивает такое разрешение, например, так:
Если пользователь снова отказывается, приложение должно либо закрыться, если ему абсолютно необходимо это разрешение, либо продолжить работу с ограниченными функциональными возможностями. Если пользователь пересматривает (и выбирает повторную попытку), разрешение запрашивается снова. На этот раз приглашение выглядит так:
Во второй раз отображается флажок "Никогда больше не спрашивать". Если пользователь снова отказывается и флажок установлен, больше ничего не должно происходить. Установлен ли флажок или нет, можно определить с помощью Activity.shouldShowRequestPermissionRationale(String), например, так:
if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) {...
Вот что говорится в документации Android ( https://developer.android.com/training/permissions/requesting.html):
Чтобы помочь найти ситуации, когда вам необходимо предоставить дополнительные пояснения, система предоставляет метод Activity.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()
,
https://youtu.be/C8lUdPVSzDk?t=2m23s
Проверьте, было ли предоставлено разрешение или нет onRequestPermissionsResult()
, Если нет, то проверьте shouldShowRequestPermissionRationale()
,
- Если этот метод возвращает
true
затем покажите объяснение, почему именно это разрешение необходимо. Затем в зависимости от выбора пользователя сноваrequestPermissions()
, - Если он вернется
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;
После того, как пользователь отметил "Не спрашивать снова", вопрос не может быть отображен снова. Но пользователю может быть объяснено, что он ранее отказал в разрешении и должен предоставить разрешение в настройках. И сослаться на него в настройках, используя следующий код:
@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;
}
Вы можете определить это, проверив, должно ли обоснование разрешения показываться внутри 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).
}
}
}
Может быть кому-то пригодится: -
Что я заметил, так это то, что если мы проверяем флаг mustShowRequestPermissionRationale() в методе обратного вызова onRequestPermissionsResult(), он показывает только два состояния.
Состояние 1:-Возврат истины: - Каждый раз, когда пользователь нажимает кнопку "Запретить" (включая самый первый раз).
Состояние 2:- Возвращает false: - если пользователь выбирает "никогда больше не спрашивать".
Если вы хотите обнаружить все "состояния" (в первый раз отказано, только что было отказано, просто было отказано с помощью "Никогда не спрашивать" или навсегда отказано), вы можете сделать следующее:
Создать 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.
Удачного кодирования:)
Метод shouldShowRequestPermissionRationale() может быть пользователем, чтобы проверить, выбрал ли пользователь опцию "никогда больше не спрашивать" и отказал в разрешении. Существует множество примеров кода, поэтому я бы лучше объяснил, как использовать его для таких целей, потому что я думаю, что его имя и его реализация усложняют его, так как это на самом деле.
Как объяснено в Запросе разрешений во время выполнения, этот метод возвращает значение true, если опция "никогда не спрашивать" видна, в противном случае - значение false; поэтому он возвращает ложь в самый первый раз, когда отображается диалоговое окно, затем со второго раза он возвращает истину, и только если пользователь отказывает в разрешении при выборе опции, в этот момент он снова возвращает ложь.
Чтобы обнаружить такой случай, вы можете обнаружить последовательность false-true-false или (более просто) у вас может быть флаг, который отслеживает начальное время, в течение которого отображается диалоговое окно. После этого этот метод возвращает true или false, где false позволит вам определить, когда выбран этот параметр.
возвращает истину или ложь в зависимости от предпочтений пользователя в предыдущих запросах разрешений.
Если пользователю просто отказано в разрешении (не навсегда), вернется
true
. Если отказано в разрешении навсегда, то возвращается. И хитрость в том, что даже если пользователь предоставил разрешение, функция вернется.
Таким образом, мы можем объединить оба условия, чтобы выбрать вариант «Никогда больше не спрашивать».
Итак, если пользователю не разрешено разрешение и
shouldShowRequestPermissionRationale
возвращается
false
тогда это означает, что пользователь предпочитает никогда больше не запрашивать разрешение.
Ты можешь использовать
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();
}
}
}
}
Полное объяснение для каждого случая разрешения
/**
* 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.
}
}
}
Полезная функция, чтобы определить, было ли заблокировано произвольное разрешение для запроса (в Kotlin):
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
}
Использование этого требует установки логического параметра с общими предпочтениями с названием желаемого разрешения (например, 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 в первом запросе на разрешение), чтобы различать состояния "Никогда не спрашивал" и "Никогда не спрашивать", так как предыдущая строка не возвращает эту информацию.
Метод OnRequestPermissionResult-free и shouldShowRequestPermissionRationale-free:
public static void requestDangerousPermission(AppCompatActivity activity, String permission) {
if (hasPermission(activity, permission)) return;
requestPermission();
new Handler().postDelayed(() -> {
if (activity.getLifecycle().getCurrentState() == Lifecycle.State.RESUMED) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse("package:" + context.getPackageName()));
context.startActivity(intent);
}
}, 250);
}
Открывает настройки устройства через 250 мс, если не появилось всплывающее окно с разрешениями (как в случае, если было выбрано "Больше не спрашивать".
То, как я это понял, было для меня немного новым. Я должен сохранить ссылку, если пользователь когда-либо выбирал одно решение. Таким образом, если разрешение не предоставлено, я могу сказать, что пользователь находится там впервые, и ему будет предложено увидеть всплывающее окно с разрешением, или пользователь временно или постоянно отклонил его.
псевдокод:
if( granted ) {
// you are set
} else if( requiresRationale() ) {
// in the ui let the user know he has to tap and launch permission
button.onSetClickListener { requestPermission() }
} else if( sharedPreferences.getBoolean("permission", false) ) {
// so user has already decided to deny permission, then it is permanent
launchAppSettings()
} else {
// user's first encounter, request permission
requestPermission()
}
демо прилагается в виде gif в файле readme. https://github.com/juanmendez/android-sdk-updates/tree/api/android-permissions/сингл
Пожалуйста, не бросайте в меня камни для этого решения.
Это работает, но немного "взломано".
Когда вы звоните requestPermissions
зарегистрировать текущее время.
mAskedPermissionTime = System.currentTimeMillis();
Затем в onRequestPermissionsResult
если результат не предоставлен, проверьте время еще раз.
if (System.currentTimeMillis() - mAskedPermissionTime < 100)
Так как пользователь не может так быстро нажать на кнопку "Отклонить", мы знаем, что он выбрал "никогда больше не спрашивать", потому что обратный вызов мгновенный.
Используйте на свой страх и риск.
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
}
}
}
}
}
}
Я написал сокращение для запроса разрешения в 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
Попробуйте эту простую библиотеку разрешений. Он будет обрабатывать все операции, связанные с разрешением, в 3 простых шага. Это сэкономило мое время. Вы можете закончить все связанные с разрешением работы за 15 минут.
Он может обрабатывать Запретить, может обрабатывать Никогда не спрашивать снова, Может вызывать настройки приложения для разрешения, Может выдавать сообщение Rational, Может выдавать сообщение Отказ, Может давать список принятых разрешений, Может давать список отклоненных. разрешения и т. д.
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();
}
};
Вы можете слушать красавицу.
слушатель
interface PermissionListener {
fun onNeedPermission()
fun onPermissionPreviouslyDenied(numberDenyPermission: Int)
fun onPermissionDisabledPermanently(numberDenyPermission: Int)
fun onPermissionGranted()
}
MainClass для разрешения
class PermissionUtil {
private val PREFS_FILENAME = "permission"
private val TAG = "PermissionUtil"
private fun shouldAskPermission(context: Context, permission: String): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val permissionResult = ActivityCompat.checkSelfPermission(context, permission)
if (permissionResult != PackageManager.PERMISSION_GRANTED) {
return true
}
}
return false
}
fun checkPermission(context: Context, permission: String, listener: PermissionListener) {
Log.i(TAG, "CheckPermission for $permission")
if (shouldAskPermission(context, permission)) {
// Load history permission
val sharedPreference = context.getSharedPreferences(PREFS_FILENAME, 0)
val numberShowPermissionDialog = sharedPreference.getInt(permission, 0)
if (numberShowPermissionDialog == 0) {
(context as? Activity)?.let {
if (ActivityCompat.shouldShowRequestPermissionRationale(it, permission)) {
Log.e(TAG, "User has denied permission but not permanently")
listener.onPermissionPreviouslyDenied(numberShowPermissionDialog)
} else {
Log.e(TAG, "Permission denied permanently.")
listener.onPermissionDisabledPermanently(numberShowPermissionDialog)
}
} ?: kotlin.run {
listener.onNeedPermission()
}
} else {
// Is FirstTime
listener.onNeedPermission()
}
// Save history permission
sharedPreference.edit().putInt(permission, numberShowPermissionDialog + 1).apply()
} else {
listener.onPermissionGranted()
}
}
}
Используется этим способом
PermissionUtil().checkPermission(this, Manifest.permission.ACCESS_FINE_LOCATION,
object : PermissionListener {
override fun onNeedPermission() {
log("---------------------->onNeedPermission")
// ActivityCompat.requestPermissions(this@SplashActivity,
// Array(1) { Manifest.permission.ACCESS_FINE_LOCATION },
// 118)
}
override fun onPermissionPreviouslyDenied(numberDenyPermission: Int) {
log("---------------------->onPermissionPreviouslyDenied")
}
override fun onPermissionDisabledPermanently(numberDenyPermission: Int) {
log("---------------------->onPermissionDisabled")
}
override fun onPermissionGranted() {
log("---------------------->onPermissionGranted")
}
})
переопределить onRequestPermissionsResult в активности или fragmnet
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == 118) {
if (permissions[0] == Manifest.permission.ACCESS_FINE_LOCATION && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getLastLocationInMap()
}
}
}
Я нашел для многих длинный и запутанный ответ, и после прочтения нескольких ответов мой вывод
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.READ_EXTERNAL_STORAGE))
Toast.makeText(this, "permanently denied", Toast.LENGTH_SHORT).show();
Вместо этого вы получите обратный вызов на onRequestPermissionsResult()
как PERMISSION_DENIED, когда вы снова запрашиваете разрешение, находясь в ложном состоянии shouldShowRequestPermissionRationale()
Из Android док:
Когда система просит пользователя предоставить разрешение, пользователь имеет возможность сказать системе не запрашивать это разрешение снова. В этом случае, когда приложение использует requestPermissions()
чтобы снова запросить это разрешение, система немедленно отклоняет запрос. Система вызывает ваш onRequestPermissionsResult()
метод обратного вызова и проходы PERMISSION_DENIED
Точно так же, как если бы пользователь снова явно отклонил ваш запрос. Это означает, что когда вы звоните requestPermissions()
Вы не можете предполагать, что какое-либо прямое взаимодействие с пользователем имело место.
Чтобы точно ответить на вопрос, что происходит, когда пользователь нажимает "Никогда не спрашивать"?
Переопределенный метод / функция
onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray)
Массив grantResult оказывается пустым, так что вы можете что-то сделать там, может быть? Но не лучшая практика.
Как обращаться с "Никогда не спрашивать снова"?
Я работаю с Fragment, для которого требуется разрешение READ_EXTERNAL_STORAGE.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
when {
isReadPermissionsGranted() -> {
/**
* Permissions has been Granted
*/
getDirectories()
}
isPermissionDeniedBefore() -> {
/**
* User has denied before, explain why we need the permission and ask again
*/
updateUIForDeniedPermissions()
checkIfPermissionIsGrantedNow()
}
else -> {
/**
* Need to ask For Permissions, First Time
*/
checkIfPermissionIsGrantedNow()
/**
* If user selects, "Dont Ask Again" it will never ask again! so just update the UI for Denied Permissions
*/
updateUIForDeniedPermissions()
}
}
}
Другие функции тривиальны.
// Is Read Write Permissions Granted
fun isReadWritePermissionGranted(context: Context): Boolean {
return (ContextCompat.checkSelfPermission(
context as Activity,
Manifest.permission.READ_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED) and
(ContextCompat.checkSelfPermission(
context,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED)
}
fun isReadPermissionDenied(context: Context) : Boolean {
return ActivityCompat.shouldShowRequestPermissionRationale(
context as Activity,
PermissionsUtils.READ_EXTERNAL_STORAGE_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();
}
Выше кода также покажет диалоговое окно, которое перенаправит пользователя на экран настроек приложения, откуда он может дать разрешение, если выбрал кнопку "Никогда не спрашивать".
Я должен реализовать динамическое разрешение для камеры. Где происходит 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;
}
}
Я также хотел бы получить информацию о том, выбрал ли пользователь "никогда не спрашивать". Я достиг "почти решения" с уродливым флагом, но прежде чем я расскажу вам, как, я расскажу вам о моей мотивации:
Я хотел бы предложить разрешение ссылаясь на функциональность изначально. Если пользователь использует его и не имеет прав, он получает 1-й диалог сверху или 2-й и 3-й. Когда пользователь выбрал "Никогда не спрашивать", я хотел бы отключить эту функцию и отобразить ее по-другому. - Мое действие вызвано вводом текста с вращением, я также хотел бы добавить "(Отозвано разрешение)" к отображаемому тексту метки. Это показывает пользователю: "Функциональность есть, но я не могу ее использовать из-за настроек разрешения". Тем не менее, это не представляется возможным, так как я не могу проверить, был ли выбран вариант "Никогда больше не спрашивать".
Я пришел к решению, с которым я могу жить, когда моя функция всегда включена с активной проверкой прав доступа. Я показываю сообщение Toast в onRequestPermissionsResult() в случае отрицательного ответа, но только если я не показывал свое настраиваемое обоснование. Поэтому, если пользователь выбрал "Никогда не спрашивать", он получает только тостовое сообщение. Если пользователь не хочет выбирать "никогда больше не спрашивать", он получает только настраиваемое обоснование и всплывающее окно с запросом на разрешение операционной системой, но не тост, поскольку три уведомления подряд будут слишком болезненными.
Расширяя ответ 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);
}
Вы можете прочитать официальный документ Android Запрос приложения разрешений
или вы можете найти много популярных библиотек разрешений Android на Github
Я немного опоздал, я столкнулся с аналогичной проблемой. Решил эту проблему, как показано ниже
Предположим, вам нужно разрешение на определение местоположения
Запускать запрос разрешений
private final ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(
new RequestPermission(),
isGranted -> {
if (isGranted) {
// Permission is granted go ahead
} else {
shouldShowRequestPermissionRationale();
}
});
Проверка разрешений
private boolean hasPermissions() {
if (checkSelfPermission(requireActivity(), ACCESS_FINE_LOCATION) == PERMISSION_GRANTED) {
// Permission is granted go ahead
} else {
requestPermissionLauncher.launch(ACCESS_FINE_LOCATION);
}
}
Проверьте, нужно ли показывать рациональное / настраиваемое диалоговое окно разрешений для обучения пользователя
private void shouldShowRequestPermissionRationale() {
if (!shouldShowRequestPermissionRationale(ACCESS_FINE_LOCATION)) {
// need to show permission rational custom dialog.
}
}