Android M Permissions : Confused on the usage of shouldShowRequestPermissionRationale() function
I was going through the official doc about the new Permissions model in Android M. It talks about the shouldShowRequestPermissionRationale()
function which returns true
if the app has requested this permission previously and the user denied the request. If the user turned down the permission request in the past and chose the Don't ask again option, this method returns false
.
But how can we differentiate between the following two cases?
Case 1: The app doesn't have a permission and the user has not been asked for the permission before. In this case, shouldShowRequestPermissionRationale() will return false because this is the first time we're asking the user.
Case 2: The user has denied the permission and selected "Don't ask again", in this case too shouldShowRequestPermissionRationale() will return false.
I would want to send the user to the App's settings page in Case 2. How do i go about differentiating these two cases?
After M Preview 1, if the dialog is displayed for the first time, there is no Never ask again checkbox.
If the user denies the permission request, there will be a Never ask again checkbox in the permission dialog the second time permission is requested.
So the logic should be like this:
-
Request permission:
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE); } else { //Do the stuff that requires permission... }
-
Check if the permission was denied or granted in
onRequestPermissionsResult
.If the permission was denied previously, this time there will be a Never ask again checkbox in the permission dialog.
Call
shouldShowRequestPermissionRationale
to see if the user checked Never ask again.shouldShowRequestPermissionRationale
method returns false only if the user selected Never ask again or device policy prohibits the app from having that permission:if (grantResults.length > 0){ if(grantResults[0] == PackageManager.PERMISSION_GRANTED) { //Do the stuff that requires permission... }else if (grantResults[0] == PackageManager.PERMISSION_DENIED){ // Should we show an explanation? if (ActivityCompat.shouldShowRequestPermissionRationale(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { //Show permission explanation dialog... }else{ //Never ask again selected, or device policy prohibits the app from having that permission. //So, disable that feature, or fall back to another situation... } } }
So, you won't have to track if a user checked Never ask again or not.
I had the same problem and I figured it out. To make life much simpler, I wrote an util class to handle runtime permissions.
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();
}
}
And the PreferenceUtil methods are as follows.
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);
}
Now, all you need is to use the method checkPermission with proper arguments.
Here is an example,
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();
}
});
Case 1: The app doesn't have a permission and the user has not been asked for the permission before. In this case, shouldShowRequestPermissionRationale() will return false because this is the first time we're asking the user.
Case 2: The user has denied the permission and selected "Don't ask again", in this case too shouldShowRequestPermissionRationale() will return false.
I would want to send the user to the App's settings page in Case 2. How do i go about differentiating these two cases?
You'll get callback on onPermissionAsk for case 1, and onPermissionDisabled for case 2.
Happy coding :)
UPDATE
I believe that CanC's answer below is the correct one that should be followed. The only way to know for sure is to verify this in the onRequestPermissionResult callback using shouldShowPermissionRationale.
==
My original answer:
The only way that I have found is to keep track on your own of whether this is the first time or not (e.g. using shared preferences). If it's not the first time, then use shouldShowRequestPermissionRationale()
to differentiate.
Also see: Android M - check runtime permission - how to determine if the user checked "Never ask again"?