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
checkPermissionagain (click on scann button) - Result object has property
deniedset 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. CheckPermissionResultgets a new propertyshowRationalewhich is set to the result ofshouldShowRequestPermissionRationaleifforceis false.- If the App receives
showRationale: truefromcheckPermission({ 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 }). Ifgrantedis false, we do not have the permission and need to gracefully degrate. Ifgrantedis 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
forceis false, setshowRationaleto false and wait for the app to callcheckPermissionagain withforce: true - If
forceis true, callrequestAccessand 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.