Android M Permissions:Confused on the Usage of Shouldshowrequestpermissionrationale() Function

Android M Permissions : Confused on the usage of shouldShowRequestPermissionRationale() function

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:

  1. 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...
    }
  2. 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.

Working of shouldShowRequestPermissionRationale method

To answer both your questions:

You can first use checkSelfPermission() to see if the permission has already been granted. If it has not been granted then you should check if shouldShowRequestPermissionRationale() returns true or false.

shouldShowRequestPermissionRationale() will return true in the following case:

  • When the user has denied the permission previously but has not
    checked the "Never Ask Again" checkbox.

shouldShowRequestPermissionRationale() will return false in the following 2 cases:

  • When the user has denied the permission previously AND never ask
    again checkbox was selected.
  • When the user is requesting permission for the first time.

So, what you can do is, if shouldShowRequestPermissionRationale() returns false
you can use a boolean preference value (default value as true) to check for

  • first time request of permission in the else case, if it is the first
    request, then trigger requestPermissions

  • else if it is not the first request and the user has previously denied the request and also has checked the "Never ask again" checkbox, you can show a simple toast with the reason for unavailability of the feature that requires the permission and also mention the steps to manually enable it via settings.

Something like this:

    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
if(isFirstTimeRequest){
// No explanation needed; request the permission

// RESET PREFERENCE FLAG

ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);

// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
} else {
// User denied previously and has checked "Never ask again"
// show a toast with steps to manually enable it via settings
}
}

Android shouldShowRequestPermissionRationale has a bug?

From Developers Docs :

shouldShowRequestPermissionRationale()

This method returns true if the app has requested this permission previously and the user denied the request.

Note: If the user turned down the permission request in the past and chose the Don't ask again option in the permission request system dialog, this method returns false.

The issue is that you haven't requested the permission before using

 ActivityCompat.requestPermissions();

Hence its not showing the dialog.

When you manually give permission or deny permission from Settings its assumed that you denied the permission and thats why it showing the Alert Dialog.

shouldShowRequestPermissionRationale is false

shouldShowRequestPermissionRationale() also returns false if the user has never been asked for permission before. That's why ideally you first ask for permission, you then check what the permission result object is through onRequestPermissionsResult, and follow any consequent request with the check for shouldShowRequestPermissionRationale() to see whether the user has checked the never ask again option.

EDIT-
Check the example in this answer:
https://stackoverflow.com/a/34612503/10300673

What is the difference between shouldShowRequestPermissionRationale and requestPermissions?

As per the documentation,

shouldShowRequestPermissionRationale return the boolean indicating whether >or not we should show UI with rationale for requesting a permission.

This UI is our custom UI (we can show an alertdialog, for example), NOT the dialog that our device shows (see below):

Allow SnazzyApp to access your contacts ? //this is NOT our custom UI

With this in mind, now

The return value of shouldShowRequestPermissionRationale is as shown in flowchart. Sample Image

Also note that,

When that user "denies"  your permission by CHECKING "never ask again", ``shouldShowRequestPermissionRationale`` would still return ``false``. 

Thus, to summarize

  • shouldShowRequestPermissionRationale will return true only if the application was launched earlier and the user "denied" the permission WITHOUT checking "never ask again".
  • In other cases (app launched first time, or the app launched earlier too and the user denied permission by checking "never ask again"), the return value is false.

Implementation

Let's create a PermissionUtils.java file which handles the different cases for us.

public class PermissionUtils {

private static final String TAG = "PermissionUtils";

/*
Inside this shared_preference file, we will just store information
about whether the user had visited our app earlier or not.
*/

private static final String PREFS_FILE_NAME = "preference_permission";
private static final String PREFS_FIRST_TIME_KEY = "is_app_launched_first_time";

//an interface containing 5 methods
//...the scenario in which these callback will be called is written below each method declaration.
public interface PermissionAskListener {

void onPermissionGranted();
/*
User has already granted this permission
The app must had been launched earlier and the user must had "allowed" that permission
*/

void onPermissionRequest();
/*
The app is launched FIRST TIME..
We don't need to show additional dialog, we just request for the permission..

*/

void onPermissionPreviouslyDenied();
/*
The app was launched earlier and the user simply "denied" the permission..
The user had NOT clicked "DO NOT SHOW AGAIN"
We need to show additional dialog in this case explaining how "allowing this permission" would be useful to the user
*/

void onPermissionDisabled();
/*
The app had launched earlier and the user "denied" the permission..
AND ALSO had clicked "DO NOT ASK AGAIN"
We need to show Toask/alertdialog/.. to indicate that the user had denied the permission by checking do not disturb too...
So, you might want to take the user to setting>app>permission page where the user can allow the permission..

*/

}

// preference utility methods
private static boolean getApplicationLaunchedFirstTime(Activity activity) {
SharedPreferences sharedPreferences = activity.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE);
return sharedPreferences.getBoolean(PREFS_FIRST_TIME_KEY, true);
}

private static void setApplicationLaunchedFirstTime(Activity activity) {
SharedPreferences sharedPreferences = activity.getSharedPreferences(PREFS_FILE_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(PREFS_FIRST_TIME_KEY, false);
editor.commit();
}

private static boolean isRuntimePermissionRequired() {
return (Build.VERSION.SDK_INT >= 23);
}

public static void checkPermission(Activity activity, String permission, PermissionAskListener permissionAskListener) {

Log.d(TAG, "checkPermission");

if (!isRuntimePermissionRequired()) {
/*
Runtime permission not required,
THE DEVICE IS RUNNING ON < 23, So, no runtime permission required..
Simply call **** permissionAskListener.onPermissionGranted() ****
*/

permissionAskListener.onPermissionGranted();
} else {
//runtime permission required here...

//check if the permission is already granted, i.e the application was launched earlier too, and the user had "allowed" the permission then.
if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
/* We don't have permission, two cases arise:
1. App launched first time,
2. App launched earlier too, and the user had denied the permission is last launch
2A. The user denied permission earlier WITHOUT checking "Never ask again"
2B. The user denied permission earlier WITH checking "Never ask again"
*/

if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {

/*
shouldShowRequestPermissionRationale returned true
this means Case: 2A
see the flowchart, the only case when shouldShowRequestPermissionRationale returns "true", is when the application was launched earlier too and the user had "denied" the permission in last launch WITHOUT checking "never show again"
*/

permissionAskListener.onPermissionPreviouslyDenied();
} else {
/*
this means, either -
Case: 1 or Case 2B
See Flowchart, shouldShowRequestPermissionRationale returns false, only when app is launched first time (Case: 1) or app was launched earlier too and user HAD checked "Never show again" then (Case: 2B)
*/
if (getApplicationLaunchedFirstTime(activity)) {

//Case: 1
Log.d(TAG, "ApplicationLaunchedFirstTime");

setApplicationLaunchedFirstTime(activity); // ** DON'T FORGET THIS **
permissionAskListener.onPermissionRequest();

} else {
//Case: 2B
Log.d(TAG, "onPermissionDisabled");

permissionAskListener.onPermissionDisabled();
}
}

} else {
Log.d(TAG, "Permission already granted");

permissionAskListener.onPermissionGranted();
}
}
}
}

Logic

  1. We first begin by checking do we even require runtime permission in the first place ? This is done by :

    if (!isRuntimePermissionRequired()) {...}
  2. If we do require runtime permission, then we check if we had got that permission already earlier by

    ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED)
  3. If we don't have the permission, then we need to handle two cases which are:

    1. App launched first time, 
    2. App launched earlier too, and the user had denied the permission is last launch
    2A. The user denied permission earlier WITHOUT checking "Never ask again".
    2B. The user denied permission earlier WITH checking "Never ask again".

Thus, the bottomline is:

Inside our PermissionUtils.java, we have an defined an interface which contains 5 abstract methods. These methods are the callbacks which will be called in different cases as discussed above.

Finally inside our activity, we handle all these cases by implementing the callbacks of the listener.

    PermissionUtils.checkPermission(MainActivity.this,
Manifest.permission.ACCESS_FINE_LOCATION,
new PermissionUtils.PermissionAskListener() {
@Override
public void onPermissionGranted() {
updateUI();

}

@Override
public void onPermissionRequest() {

ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
My_PERMISSION_ACCESS_FINE_LOCATION);

}

@Override
public void onPermissionPreviouslyDenied() {

//Show an alert message and "request the permission" in its "setPositiveButton"
//...and in "setOnNegativeButton", just cancel the dialog and do not run the
//...functionality that requires this permission (here, ACCESS_FINE_LOCATION)
new AlertDialog.Builder(MainActivity.this)
.setTitle("Permission required")
.setMessage("Location is required for this application to work ! ")
.setPositiveButton("Allow", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
My_PERMISSION_ACCESS_FINE_LOCATION);

}

})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
finish();
}
})
.show();

}

@Override
public void onPermissionDisabled() {

new AlertDialog.Builder(MainActivity.this)
.setTitle("Permission Disabled")
.setMessage("Please enable the permission in \n Settings>Uber>Permission \n and check 'location' permission")
.setPositiveButton("Go to settings", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startActivity(new Intent(Settings.ACTION_SETTINGS));

}

})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
finish();
}
})
.show();

}
});

}

Hope this helps.



Related Topics



Leave a reply



Submit