Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Library returns 'permanentlyDenied' when dialog is dismissed #709

Closed
1 of 2 tasks
DarkMikey opened this issue Oct 18, 2021 · 15 comments
Closed
1 of 2 tasks

Library returns 'permanentlyDenied' when dialog is dismissed #709

DarkMikey opened this issue Oct 18, 2021 · 15 comments
Labels
platform: android Issue is related to the Android platform. status: triage Indicates that this issue needs to be analyzed and decorated with the appropriate labels

Comments

@DarkMikey
Copy link

DarkMikey commented Oct 18, 2021

馃悰 Bug Report

permission_handler 8.1.6 returns PermissionStatus.permanentlyDenied instead of PermissionStatus.denied if the dialog is dismissed. This is a contradiction to this statement here: "PermissionStatus.permanentlyDenied means permissions have been denied and the user should grant permissions through the OS "Settings" (permissions dialogs will not be shown when requesting permission).". Dialogs are shown if I click that again.
https://user-images.githubusercontent.com/31889204/137786586-780d8626-4473-4ff7-a3e4-a54318e07e66.mp4

This could lead to a second dialog that points the user to the settings:
https://user-images.githubusercontent.com/31889204/137789474-e026bce9-4a1c-43df-b0c5-3872b23b7f6d.mp4

I've read the following, but I'm unsure how to proceed:

Expected behavior

I guess this should return PermissionStatus.denied.

Reproduction steps

  1. Status: denied (freshly initialized)
  2. -> request
  3. -> dismiss
  4. Status: permanentlyDenied
TextButton(
  onPressed: () async {
    Permission permission = Permission.locationWhenInUse;
    status = await permission.request();
    setState(() {});
  },
  child: Text('Get Location Permission Status'),
),

Version: 8.1.6

Platform:

  • 馃摫 iOS
  • 馃 Android
@DarkMikey
Copy link
Author

@mvanbeusekom Is this normal behavior and I should work around it or a bug?
Thanks for your time and energy to build this!

@mvanbeusekom
Copy link
Member

@DarkMikey sorry I will have to look into this and see how we can catch this specific situation. Unfortunately I didn't find the time yet to do.

@mvanbeusekom mvanbeusekom added platform: android Issue is related to the Android platform. status: triage Indicates that this issue needs to be analyzed and decorated with the appropriate labels labels Nov 9, 2021
@mvanbeusekom
Copy link
Member

Hi @DarkMikey,

I have been debugging this issue a bit and the following is happening when you dismiss the permission dialog (in my case I press the Android back button).

NOTE: It is important to know that Android only reports if permissions are PackageManager.PERMISSION_GRANTED or PackageMaanger.PERMISSION_DENIED. There is no official API to check if permissions are permanently denied or not. To determine this we employ a workaround that uses the ActivityCompat.shouldShowRequestPermissionRationale() method.

  • When the permission dialog is dismissed, Android calls the onRequestPermissionsResult callback with the status PackageManager.PERMISSION_DENIED, the same way it would if the end-user presses the "Deny" button);
  • Next step the permission_handler does is check the result of the ActivityCompat.shouldShowRequestPermissionRationale() method. This is used as a work around to detect if we can request permissions again or if permissions are denied permanently. In case the end-user presses the "Deny" button this method would return true, however in the case the dialog is dismissed this method returns false. This is when the permission_handler determines it is no longer allowed to request permissions and returns the PermissionStatus.permanentlyDenied status.

Since the permission dialog is fully controlled by Android (separate intent and activity) we cannot override the back button logic or detect if it is pressed.

This means that at the moment I don't see a way around this issue. The only option would be to remove the check on the ActivityCompat.shouldShowRequestPermissionRationale() method which means we will not be able to report that permission is PermissionStatus.permanentlyDenied. This would be a huge breaking change and I am not sure we should go there.

For now I will close this issue as I don't see how we could solve this. However feel free to post your thoughts on this and if there is something I overlooked I would be more then happy to reconsider and see if we can make a better implementation.

@DarkMikey
Copy link
Author

Thank you that you took the time to look into this.
I guess this is fine, I just wanted to know if we were doing something wrong.
Why is this such a pain? Hopefully this will get easier in the future.

@Nightbl927
Copy link

@mvanbeusekom is there a way to return information that the dialog was closed without a selection?

@mvanbeusekom
Copy link
Member

@Nightbl927 not to my knowledge unfortunately. If you know of a way or find something I would be very interested.

@Nightbl927
Copy link

@mvanbeusekom I'll have to take a look at the code to see if I can hack a way to do so. Thanks for your hard work!

@kostadin24
Copy link

kostadin24 commented Jul 20, 2022

Hi
I managed to check and ask for location permission. Added 'show app settings' to allow user to grant permissions in case of permanent deny. But My logic suffer only when user just denied for second time. Then I don't know if permanent deny is new state or existed before.

Here is my code:

  Future<bool> _askLocationPermission() async {
    PermissionStatus locationStatus = await Permission.location.status;
    if (locationStatus.isDenied) {
		locationStatus = await Permission.location.request();
      if (locationStatus.isPermanentlyDenied) {
        //TODO: How to understand if deny was permanent before last request()?

        bool canBeOpened = await openAppSettings();
        //State of permission after finishing 'openAppSettings' screen is handled in 'didChangeAppLifecycleState'
		return false;
      }
    }

    return locationStatus == PermissionStatus.granted;
  }

@mvanbeusekom
Copy link
Member

Hi @kostadin24,

Not sure if I understand correctly, however if there are two situations:

  1. On Android 30+, if the user denied for the second time Android will deny permissions permanently.
  2. On Android 29 and lower the user will be presented with an extra checkbox in the permission dialog saying Don't ask me again. If the user ticks this box Android will deny permissions permanently.

In both situations the Permission.location.request() method will correctly report back the PermissionStatus.permanentlyDenied status (e.i. the locationStatus.isPermanentlyDenied property will return true).

In all other cases you are free to request permissions again and Android will show the permission dialog. Note that is you request permissions while the permissions are actually permanently denied, Android will not show a permission dialog to the user and the permission_handler will immediately return with a PermissionStatus.permanentlyDenied status. Meaning it is save to call Permission.location.request() even if the actual status is permanentlyDenied.

@kostadin24
Copy link

kostadin24 commented Jul 20, 2022

I call openAppSettings when detect permanentDenied.
But there is a corner case: user just said Dont show again; I have to skip openAppSettings in this case, because it is anoying to open app settings when user just refused permissions.
I think I found answer. shouldShowRequestRationale switches from true to false when execution of this code triggers permanent deny to be applied.
So at moment when I enter if clause for permanent deny - status is isPermanentlyDenied and shouldShowRequestRationale value is switched from True to false.
With this I know if this permission state is new - to skip openAppSettings, or it is from before and I must show openAppSettings

Thanks

@mvanbeusekom
Copy link
Member

You should test this out carefully, the shouldShowRequestRationale is not the most reliable solution. Personally I would keep track of a boolean value and validate that.

You could even store this state on the device (e.g. by using the shared_preferences plugin) and use the boolean over multiple app restarts. This way you have full control and you don't have to rely on some magic value returned by shouldShowRequestRationale

@kightsonsanom
Copy link

kightsonsanom commented Sep 6, 2023

@mvanbeusekom I still get PermissionStatus.permanentlyDenied on Android 13 when dismissing popup for bluetooth permissions. This happens even if app is run for the first time after instalation. Here is sample code:

 Future<bool> requestBluetoothPermission() async {
    final permissionResult = await [
      Permission.bluetoothScan,
      Permission.bluetoothConnect,
    ].request();

    if (permissionResult.entries.every((element) => element.value.isGranted)) {
      return true;
    }

my compileSdkVersion is set to 33

@JeroenWeener
Copy link
Contributor

Hi @kightsonsanom! This is likely because the latest version of the plugin doesn't include the latest Android update that is mentioned in #1125. I have opened #1150 to bring the app-facing package up-to-date.

@kightsonsanom
Copy link

I confirm that it works fine on version 11.0.0, thanks!

@JeroenWeener
Copy link
Contributor

Thanks for confirming!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
platform: android Issue is related to the Android platform. status: triage Indicates that this issue needs to be analyzed and decorated with the appropriate labels
Projects
None yet
Development

No branches or pull requests

6 participants