How to avoid requestPermission() if the user has checked "Never ask again"? - android

I'm dealing with on demand permissions in Android and I found a confussing situation. My objective is to display a dialog with a "go to app settings" button if the user has selected "Never ask again" in a permission.
OK, if the user selects "Never ask again" then, next time you request the permission you can know it doing this in the method onRequestPermissionsResult
public boolean checkIfShouldShowGoToSettingsDialog(String permissions[], int[] grantResults){
for (int i=0; i<permissions.length; i++){
if (grantResults[i] == PackageManager.PERMISSION_DENIED && !ActivityCompat.shouldShowRequestPermissionRationale(SectionManager.getInstance().getCurrentActivity(), permissions[i])) {
return true;
}
}
return false;
}
The problem is that if you do that, each time you call requestPermission(), then, onRequestPermissionsResult is being called again even with the permission denied previously and even with the checkbox selected. So checkIfShouldShowGoToSettingsDialog is returning true because the permission is DENIED and shouldShowRequestPermissionRationale is returning false. I don't want that. I want to display the "go to app settings" dialog only the first time the user has selected "Dont ask again".
So I tryed something:
I added this validation before calling requestPermission(). I tryed removing the permissions which returns false to the method shouldShowRequestPermissionRationale() because it's supossed that those permissions has the checkbox selected:
public String[] removePermanentlyDeniedPermissionsFromArray(String[] permissions){
List<String> result = new ArrayList<>();
for (String permission : permissions) {
if (ActivityCompat.shouldShowRequestPermissionRationale(SectionManager.getInstance().getCurrentActivity(), permission)) {
result.add(permission);
}
}
return result.toArray(new String[result.size()]);
}
It worked perfectly but... the problem is that the first time you open the app, the first time you are going to request the permissions... shouldShowRequestPermissionRationale() returns FALSE for all of them!! so no permissions are being requested.
Then, how can i avoid a callrequestPermission() if the user has checked "Never ask again" ?

I am very happy to answer your questions. In my past projects, I also encountered similar problems. The following is my solution.
(1) In order to avoid duplicate prompts, we must save a successful token when the user first agrees. Wait until the next operation, first determine whether the user has agreed, if you have agreed, then no longer prompt. Otherwise, prompt the user again.
(2) Here we need to use an open source framework for quickly saving reminder tags. Why use this framework? Because it has a method that can set the validity period of a tag directly. Specific usage can be viewed here: https://github.com/yangfuhai/ASimpleCache

So here is the wrong use of shouldShowPermissionRationale. That is true if permission was denied earlier (without clicking on check box). For first time it is always false. Check the doc
/**
* Gets whether you should show UI with rationale for requesting a permission.
* You should do this only if you do not have the permission and the context in
* which the permission is requested does not clearly communicate to the user
* what would be the benefit from granting this permission.
* <p>
* For example, if you write a camera app, requesting the camera permission
* would be expected by the user and no rationale for why it is requested is
* needed. If however, the app needs location for tagging photos then a non-tech
* savvy user may wonder how location is related to taking photos. In this case
* you may choose to show UI with rationale of requesting this permission.
* </p>
*
* #param activity The target activity.
* #param permission A permission your app wants to request.
* #return Whether you can show permission rationale UI.
*
* #see #checkSelfPermission(android.content.Context, String)
* #see #requestPermissions(android.app.Activity, String[], int)
*/
Process for requesting runtime permission
1.Generate the list of Permissions which are not granted.
List<String> permissionList = new ArrayList<>()
if(somePermission == PackageManager.PERMISSION_DENIED){
permissionList.add(somePermission);
}
//so on
String[] permissionArray = permissionList.toArray(new String[permissionArray.size()]);
2.In case of multiple permissions being asked at the same time, rationale might get complicated, So simply ignore it.
3.Request the permission with above generated Array.
4.In case permission is denied, redirect the user to settings screen if permission is mandatory for the app to run.
Best Practice:
Always group the permissions that are mandatory and optional within the app and ignore the rejections for optional permissions.

Related

Permission request flutter app after denial

I'm developing a flutter application, I need to manage the permission request, but I don't know how to treat a particular occurrence:
If I deny two times the same permission through the popup it could be impossible approve it later 'cause the popup will not appair again.
Future<void> requestStoragePermission() async{
var status = await Permission.storage.status;
if(status.isPermanentlyDenied){
await AppSettings.openAppSettings();
} else {
await Permission.storage.request();
}
}
I don't understand how to distinguish when the permission has not yet been granted or when it has been refused several times because the function: Permission.storage.status always returns "denied".
****** EDIT ******
The problem arises when the user refuses the same permission several times (2 times) because the permissions request popup is no longer shown, in which case it is necessary to manually open the application settings and modify the permissions by hand. I have to make sure that: the first two times I request permissions with the popup then I should open the settings screen
I have always managed my permissions using two statuses granted and limited (used only for iOS14+). These two permissions are the only truethy statuses. All the others are falsey statuses.
the permission_handler package handles a lot of logic for you already. Before it makes the request, it will check the status to see if it is already defined. If it is, then it will return the status. If the permission has never been requested, then it will request the permission.
Personally, I set up a generic method for a permission request, to keep things DRY.
Future<bool> requestPermission(Permission setting) async {
// setting.request() will return the status ALWAYS
// if setting is already requested, it will return the status
final _result = await setting.request();
switch (_result) {
case PermissionStatus.granted:
case PermissionStatus.limited:
return true;
case PermissionStatus.denied:
case PermissionStatus.restricted:
case PermissionStatus.permanentlyDenied:
return false;
}
}
I then make a request like
final canUseStorage = await requestPermission(Permission.storage);
if (canUseStorage) {
// do something with storage
}
If you have UI that is dependent on a status from Permission, then you still call Permission.storage.status.
[EDIT]
At the moment, you can't track how many times the request pop-up has been shown via permission_handler. It only returns the status. You would need to take the user to the settings depending on the returned status value.
Side Note
Instead of taking the user directly to the settings. Maybe you show a pop up saying "Looks like we don't have permission...", with a button that the user can tap to go to the settings, provides the user with some context as to why they need to go to their settings. And it's also a better user experience!

Android: how to use shouldShowRequestPermissionRationale to check permission state?

From the doc, shouldShowRequestPermissionRationale indicates whether you should show permission rationale UI.
I found:
if change one permission to "Notify" and before first showing the permission-request-dialog, shouldShowRequestPermissionRationale return false
if deny the permission with the "Never ask again" checkbox selected, shouldShowRequestPermissionRationale return false
Then I met a problem, how to distinguish these two cases? I mean, in the following code snippet from Android developer:
if (ContextCompat.checkSelfPermission(
CONTEXT, Manifest.permission.REQUESTED_PERMISSION) ==
PackageManager.PERMISSION_GRANTED) {
// You can use the API that requires the permission.
performAction(...);
} else if (shouldShowRequestPermissionRationale(...)) {
// In an educational UI, explain to the user why your app requires this
// permission for a specific feature to behave as expected. In this UI,
// include a "cancel" or "no thanks" button that allows the user to
// continue using your app without granting the permission.
showInContextUI(...);
} else {
// You can directly ask for the permission.
requestPermissions(CONTEXT,
new String[] { Manifest.permission.REQUESTED_PERMISSION },
REQUEST_CODE);
}
In the case 2, though denied the permission with "Never ask again", the code still invokes requestPermissions?

Working of shouldShowRequestPermissionRationale method

I want to request permissions on runtime.I checked out official android developer website and it says that shouldShowRequestPermissionRationale returns true if permission was previously denied and returns false if permission has been denied AND never ask again checkbox was selected.
Then i saw this code in the site:
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 {
// No explanation needed; request the permission
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.
}
My 2 questions are:
1)What happens if the user wasn't asked permission previously??We need to ask him right??Where do you put that code??
2)The above code asks for permission even when the user checked the never ask again checkbox(when shouldShowRequestPermissionRationale returns false,I.e,in the else block).How can u ask for permission when the user has checked that option??
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
}
}

shouldShowRequestPermissionRationale always returns false

Currently I am programming an android app which reads out the phone number of the user. Because the app is developed for SDK 23+ (target sdk 27) I have to request the read sms permission. If the user denies the permission the first time a dialog should appear where the use of the phone number is explained. Then the user can choose to request the permission once more or to type the phone number manually. If the user tiks "never ask again" a dialog should appear where the user is instructed to allow the permission via the settings.
To check if the user has ticked never ask again, I use the method shouldShowRequestPermissionRationale. But the method always returns false, even if I never ticked never ask again.
Here is the relevant code:
public class SettingsSettingsFragment extends Fragment implements ActivityCompat.OnRequestPermissionsResultCallback {
public void readNumber() {
if (person.getPhoneNumber() == null || person.getPhoneNumber().equalsIgnoreCase("")) {
if (checkSelfPermission(getActivity(), READ_SMS) != PackageManager.PERMISSION_GRANTED)
requestPermission();
else {
//read phoneNumber
}
}
//request permission send sms
private void requestPermission() {
requestPermissions(new String[]{Manifest.permission.READ_SMS}, PERMISSION_REQUEST_SEND_SMS);
}
#Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
if (requestCode == PERMISSION_REQUEST_SEND_SMS)
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
// user rejected the permission
boolean showRationale = shouldShowRequestPermissionRationale(Manifest.permission.SEND_SMS);
if (!showRationale) {
// user also CHECKED "never ask again" - show dialog
//show dialog: please allow in settings
} else if (counter < 2) {
// explain the permission, and give the user the possibility to ask once more
counter++;
}
}
Why is shouldShowRequestPermissionRationale always returning false?
The code is from here: Android M - check runtime permission - how to determine if the user checked "Never ask again"?
Thanks for help in advance.
According to android developers documentation of shouldShowRequestPermissionRationale():
method 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 in the permission request system dialog, this method returns false
If you haven't requested permission before it will return false because there is no need to pop the rational alert dialog.
This answer was very helpful for me to understand the issue.
Your requestPermission() is for READ_SMS, butshouldShowRequestPermissionRationale() is about SEND_SMS.
shouldShowRequestPermissionRationale() keeps returning false until user is asked for the "relevant" permission. Once requested, it returns true until user denies the same permission with "Never Ask Again" checked.
So, in your case, SEND_SMS is never requested. Therefore shouldShowRequestPermissionRationale(Manifest.permission.SEND_SMS) will keep returning false as expected. I made the same mistake before.

Cordova runtime permissions

I'm trying to get the user to enable the location group permission for my app with Cordova's hasPermission/requestPermission methods, but the results are confusing...
When I call hasPermission with ACCESS_FINE_LOCATION, it always returns true. Calling this on Manifest.permission_group.LOCATION seems to return true/false appropriately.
Calling requestPermission with Manifest.permission_group.LOCATION doesn't present a system dialog, so I'm calling this with ACCESS_FINE_LOCATION to get the dialog.
The dialog Allow button turns on the Location group permission for my app and calls onRequestPermissionResult with PackageManager.PERMISSION_GRANTED, but the Deny button also returns ...GRANTED, leaving the Location group permission off.
For illustration, here's my current code:
private void checkPermissions() {
if (!cordova.hasPermission(Manifest.permission_group.LOCATION)) {
cordova.requestPermission(this, PERMISSION_RUNTIME_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION);
}
}
#Override
public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) throws JSONException {
if (permissions.length != 1 || grantResults.length != 1 || !Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[0])) {
throw new RuntimeException("Unexpected permission results " + Arrays.toString(permissions) + ", " + Arrays.toString(grantResults));
}
int result = grantResults[0];
String action = null;
switch (result) {
case PackageManager.PERMISSION_DENIED:
action = Constants.ACTION_RUNTIME_PERMISSION_DENIED;
break;
case PackageManager.PERMISSION_GRANTED:
action = Constants.ACTION_RUNTIME_PERMISSION_GRANTED;
break;
default:
throw new RuntimeException("Unexpected permission result int " + result);
}
Intent i = new Intent(action);
i.putExtra("permission", Constants.EXTRA_RUNTIME_PERMISSION_NOTIFICATION_ID);
getContext().sendBroadcast(i);
}
What's the right way to handle this? Sometimes using an individual permission and sometimes using a group with these methods doesn't seem right - I would expect this to be consistent. My guess is that the PERMISSION_GRANTED after the DENY button is pushed on the dialog is because I'm requesting an individual permission, which is on even though the group is off; is there a way to detect that the user denied the request?
I have a lot of questions there which basically boil down to "how do I either get the user to enable the Location group permission when it's off or know when they decline"?
If it helps, my android-targetSdkVersion is set to 22, and I'm using Cordova 6.1.1.
If it helps, my android-targetSdkVersion is set to 22, and I'm using Cordova 6.1.1.
Android run-time permissions were only introduced in API 23, so if your android-targetSdkVersion is set to 22, run-time permissions code will always return GRANTED for any permission, since permissions are granted at installation time via the manifest.
However, if your app is displaying runtime permissions dialogs, I'm guessing that you must be building against API 23 and using cordova-android#5+ for the Android platform.
Regarding permission groups vs individual permissions, you should read the Android documentation regarding runtime permissions:
The dialog box shown by the system describes the permission group your app needs access to; it does not list the specific permission. For example, if you request the READ_CONTACTS permission, the system dialog box just says your app needs access to the device's contacts. The user only needs to grant permission once for each permission group. If your app requests any other permissions in that group (that are listed in your app manifest), the system automatically grants them. When you request the permission, the system calls your onRequestPermissionsResult() callback method and passes PERMISSION_GRANTED, the same way it would if the user had explicitly granted your request through the system dialog box.
So in your case, requesting ACCESS_FINE_LOCATION grants access to all permissions in the LOCATION group (you can find the full list of groups and permissions here).
the Deny button also returns ...GRANTED, leaving the Location group permission off.
This should not return GRANTED. If the Deny button is pressed, access will be DENIED to that entire permission group (including the requested permission). The logic in your code snippet looks OK to handle this, so I would use the step-through debugger in Android Studio to see exactly what is happening in your code here.

Categories

Resources