When it comes to the M Developer Preview runtime permissions, according to Google:
If you have never asked for a certain permission before, just ask for it<
Here is method to track when permission dialog was shown first time, when user checked the never ask again and when permission is directly denied after user checked never ask again for this we need to keep a flag for if permission rationale dialog has been shown before getting result in onRequestPermissionsResult. Call method checkPermission() when required.
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);
}
}
There is no need to create a parallel persistent state for the permission state, you could just use this method which returns the current permission state at any time:
@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: returns BLOCKED the first app start, before the user accepted/denied the permission through the user prompt (on sdk 23+ devices)
I also used this answered here.
No, you don't need to track whether or not you asked for the permission, and you don't need to distinguish Never-Asked From Stop-Asking.
The state 1 and 3 are the same for app developer: you need the permission and ActivityCompat.checkSelfPermission != PackageManager.PERMISSION_GRANTED
, then you just ask for the permission via ActivityCompat.requestPermissions()
, whenever user tapped the feature that requires the permission, no matter how many times you have requested. User will eventually "Grant" it, or "Deny" it with "never ask again" checked. The design does NOT discourage you from popup the permission request dialogbox multiple times.
However, the design does encourage you to explain the purpose of the permission at some point - your state 2. shouldShowRequestPermissionRationale()
is NOT used to determine if you should request for permission, it's used to determine if you should show explanations, BEFORE you request for permission.
A couple more explanation regarding the state 3:
shouldShowRequestPermissionRationale()
.ActivityCompat.requestPermissions()
will not popup dialogbox anymore.shouldShowRequestPermissionRationale()
return false.Regarding MLProgrammer-CiM's answer, I have an idea of how to solve the scenario in which the user revokes the permission after the boolean stored in SharedPrefrences is already true,
simply create another constant boolean, if the first one called for example: Constant.FIRST_TIME_REQUEST
(which its default state will be true)
the second one will be called Constant.PERMISSION_ALREADY_GRANTED
(which will be false on default)
On onRequestPermissionsResult
if permission was granted you change its value to true, of course.
Now, in the part where you want to ask permission with pre-explanation, write something like that:
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
SharedPreferences sp = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
boolean isPermissionGranted = sp.getBoolean(Constant.PERMISSION_ALREADY_GRANTED, false);
if (isPermissionGranted) {
sp.putBoolean(Constant.PERMISSION_ALREADY_GRANTED, false);
sp.putBoolean(Constant.FIRST_TIME_REQUEST, true);
}
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, androidPermissionName) || sp.getBoolean(Constant.FIRST_TIME_REQUEST, true) ) {
showDialogExplanation();
}
}
that way even if the user will remove the permission, the boolean will be set to false again.
good luck, I hope it'll help.
Shlo
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.
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- 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();
}
I have an approach to the solution to your problem, it seems to work pretty well for me.
I Distinguish Never-Asked From Stop-Asking using the SharedPreferences, I'll give you an example of how I use that.
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
}
}
}