barcode-scanner
barcode-scanner copied to clipboard
Android 11 permission "Ask every time" doesn't prompt the user to give camera permission again
Describe the bug
On android 11 there is a possibility to set the camera permission to "Ask every time". In case this permission is set plugins checkPermission method returns status denied set to true
, only if user sets the permission manually to "Allow only while using the app" in Settings -> Apps -> [app] it is possible to scan the codes again. The issue appears after the device goes to sleep mode.
To Reproduce Steps to reproduce the behavior:
- Call the
checkPermission
- On prompt select "Only this time"
- Code scanning works as expected
- Close the app and leave device until it goes to sleep mode or turn it off
- Open cell phone, open the app and call the
checkPermission
again (click on scann button) - Result object has property
denied
set totrue
Expected behavior If permission is set to "Ask every time" the app should prompt the window with question to set the permissions again.
Screenshots If applicable, add screenshots to help explain your problem.
Version v2.0.0
Smartphone (please complete the following information):
- Device: [Samsung A70]
- OS: [Android 11]
Additional context Add any other context about the problem here.
The cause of this is the (unfortunately apparently very common) misuse of the shouldShowRequestPermissionRationale
method. It is used to detect the difference between "denied for now" and "denied forever", which is not its function at all.
Capacitor itself also makes this wrong assumption about the return value of this function: https://github.com/ionic-team/capacitor/blob/17a73ff60b3e0e1807db3a2349b83ca62d7fb83b/android/capacitor/src/main/java/com/getcapacitor/Bridge.java#L899-L905 I don't know if this issue is caused by that or not.
The guidelines regarding permissions in the readme also explicitly do it "the wrong way" compared to the official guidelines regarding "asking for permissions" on Android: https://developer.android.com/training/permissions/requesting#workflow_for_requesting_permissions
In my opinion the functionality (on Android) should be as follows:
- App invokes Plugin method
checkPermission({ force: false })
to determine whether or not the permission is already granted. If yes, great. If not, keep going. -
CheckPermissionResult
gets a new propertyshowRationale
which is set to the result ofshouldShowRequestPermissionRationale
ifforce
is false. - If the App receives
showRationale: true
fromcheckPermission({ force: false })
, it shows a rationale. If the user does not acknowledge the need, bail out here. If they do, keep going. - App invokes Plugin method
checkPermission({ force: true })
. The Plugin unconditionally requests the permission if this happens. - App receives result of
checkPermission({ force: true })
. Ifgranted
is false, we do not have the permission and need to gracefully degrate. Ifgranted
is true, great.
In Pseudocode (App):
async function hasPermission(): boolean {
const passiveResult = await BarcodeScanner.checkPermission({force: false});
// we already have the permission, great
if (passiveResult.granted) return true;
// we will never have the permission, even if we ask using force
if (passiveResult.denied || passiveResult.restricted) return false;
// if we don't need to show a rationale _or_ the user acknowledges our rationale, request the permission
if (!passiveResult.showRationale || await showPermissionRationale()) {
return (await BarcodeScanner.checkPermission({force: true})).granted;
} else {
// user denied our rationale
return false;
}
}
On iOS the story is simpler in the Plugin (see: https://developer.apple.com/documentation/avfoundation/cameras_and_media_capture/requesting_authorization_for_media_capture_on_ios section "Verify and Request Authorization for Capture", this is already correct in the current implementation):
- First check
authorizationStatus(for:)
. If it returns granted, denied or restricted set the appropriate granted/denied/restricted property. - If
authorizationStatus(for:)
returnednotDetermined
:- If
force
is false, setshowRationale
to false and wait for the app to callcheckPermission
again withforce: true
- If
force
is true, callrequestAccess
and wait for the completion handler.
- If
An Addendum: Doing this properly also simplifies things, mainly the need to keep track of "are we asking for the first time" on Android using SharedPreferences completely goes away as well as the code to open the settings app.
I'm also running into this issue, it would be nice to get a solution for this
Same issue here right now. It prevents the app from having a decent UI to handle this use case.
Is this project still maintained?
I am using the checkPermissions
/ requestPermissions
of the @capacitor/camera
module to check for permissions and then I use the barcode module to do the scanning.
@jorrit solution should do the trick for now, once I cleaned up the open issues and PRs I think it would make sense to use the same logic implemented in @capacitor/camera
for this plugin.
I will start a PoC to use ML Kit
for this plugin soon (#107), when doing this I will as well optimise the permission checks and mimic the behaviour of @capacitor/camera
since the rewrite will include a lot of breaking API changes anyways and therefore some work to update to 3.0.0
it will be possible to split the checking to checkPermissions
and requestPermissions
as well.
I have rewritten the permissions according to capacitor/camera
this should work in the upcoming release v5
as expected, this version is currently released as pre-release.
Could you please also apply this change to v4
? Since the ML Kit
used in v5
has a much larger impact on package size, it's possible that some people who are concerned about package size would prefer to use v4
. Thank you very much.