Android grants "dangerous" permissions such as location at run time. Every plugin that needs one has checkPermissions(), which reads the state without asking, and requestPermissions(), which may show the system dialog. Both return one PermissionState per alias:
| State | Meaning | What to do |
|---|---|---|
| prompt | Never asked | Explain the feature, then request |
| prompt-with-rationale | Refused once; Android may ask again | Explain why before requesting again |
| granted | Allowed | Use the feature |
| denied | Refused for good, or blocked | Point to the app's settings |
A fresh install of BookNest, checked from the DevTools console:
const { Geolocation, Camera } = Capacitor.Plugins;
console.log(JSON.stringify(await Geolocation.checkPermissions()));
console.log(JSON.stringify(await Camera.checkPermissions()));{"coarseLocation":"prompt","location":"prompt"}
{"saveGallery":"prompt","readExternalStorage":"prompt","camera":"granted","photos":"granted"}Camera reports camera as granted because the manifest does not declare the CAMERA permission: the plugin only needs it when you do, and otherwise delegates to the camera app (Camera). Tapping "Don't allow" in the location dialog of Geolocation made where() log "location":"prompt-with-rationale", and a second request showed the dialog again.
Since Android 11, a second refusal counts as "don't ask again": requestPermissions() then returns denied without a dialog, and only the app's system settings can change it. So ask in context, when the reader taps Where, not at launch, and explain before a second request. For testing, adb shell pm revoke and pm grant flip a runtime permission without the dialog. Plugins you write declare their aliases in @CapacitorPlugin(permissions = ...) (Custom Plugins).