Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Do We Distinguish Never-Asked From Stop-Asking in Android M's Runtime Permissions?

When it comes to the M Developer Preview runtime permissions, according to Google:

  1. If you have never asked for a certain permission before, just ask for it

  2. If you asked before, and the user said "no", and the user then tries doing something that needs the rejected permission, you should prompt the user to explain why you need the permission, before you go on to request the permission again

  3. If you asked a couple of times before, and the user has said "no, and stop asking" (via the checkbox on the runtime permission dialog), you should just stop bothering (e.g., disable the UI that requires the permission)

However, we only have one method, shouldShowRequestPermissionRationale(), returning a boolean, and we have three states. We need a way to distinguish the never-asked state from the stop-asking state, as we get false from shouldShowRequestPermissionRationale() for both.

For permissions being requested on first run of the app, this is not a big problem. There are plenty of recipes for determining that this is probably the first run of your app (e.g., boolean value in SharedPreferences), and so you assume that if it's the first run of your app, you're in the never-asked state.

However, part of the vision of runtime permissions is that you might not ask for all of them up front. Permissions tied to fringe features you might only ask for later on, when the user taps on something that requires that permission. Here, the app may have been run many times, for months, before we all of a sudden need to request another permission.

In those cases, are we supposed to track whether or not we asked for the permission ourselves? Or is there something in the Android M API that I am missing that tells us whether we asked before or not?

like image 476
CommonsWare Avatar asked Aug 10 '15 20:08

CommonsWare


People also ask

What are runtime permissions in Android?

Runtime permissions prevent apps from gaining access to private data without a user's consent, and provide them with additional context and visibility into the types of permissions that applications are either seeking, or have been granted.

How can I tell if an Android user is denied permission?

The method shouldShowRequestPermissionRationale() can be used to check whether the user selected the 'never asked again' option and denied the permission.

How do I request runtime permission on Android?

checkSelfPermission(String perm); It returns an integer value of PERMISSION_GRANTED or PERMISSION_DENIED. Note: If a user declines a permission that is critical in the app, then shouldShowRequestPermissionRationale(String permission); is used to describe the user the need for the permission.


1 Answers

I know I am posting very late, but detailed example may be helpful for someone.

What I have noticed is, if we check the shouldShowRequestPermissionRationale() flag in to onRequestPermissionsResult() callback method, it shows only two states.

State 1:-Return true:-- Any time user clicks Deny permissions (including the very first time.

State 2:-Returns false :- if user select s “never asks again.

Here is an example with multiple permission request:-

The app needs 2 permissions at startup . SEND_SMS and ACCESS_FINE_LOCATION (both are mentioned in manifest.xml).

As soon as the app starts up, it asks for multiple permissions together. If both permissions are granted the normal flow goes.

enter image description here

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; } 

In case one or more permissions are not granted, activityCompat.requestPermissions() will request permissions and the control goes to onRequestPermissionsResult() callback method.

You should check the value of shouldShowRequestPermissionRationale() flag in onRequestPermissionsResult() callback method.

There are only two cases:--

Case 1:-Any time user clicks Deny permissions (including the very first time), it will return true. So when the user denies, we can show more explanation and keep asking again.

Case 2:-Only if user select “never asks again” it will return false. In this case, we can continue with limited functionality and guide user to activate the permissions from settings for more functionalities, or we can finish the setup, if the permissions are trivial for the app.

CASE- 1

Case - 1

CASE- 2

Case - 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();     } 
like image 154
Nicks Avatar answered Oct 01 '22 02:10

Nicks