Skip to content

Commit c9c4c84

Browse files
fix(android): Only request permissions that are defined in the manifest (#85)
1 parent 1a2504a commit c9c4c84

4 files changed

Lines changed: 60 additions & 23 deletions

File tree

README.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ Apple requires privacy descriptions to be specified in `Info.plist` for location
1616
- `NSLocationAlwaysAndWhenInUseUsageDescription` (`Privacy - Location Always and When In Use Usage Description`)
1717
- `NSLocationWhenInUseUsageDescription` (`Privacy - Location When In Use Usage Description`)
1818

19-
> [!NOTE]
20-
> This Capacitor plugin does not support background geolocation directly. However, it relies on
21-
> [`ion-ios-geolocation`](https://github.com/ionic-team/ion-ios-geolocation), which can report
22-
> location in the background. As a result, Apple requires you to include a
23-
> `NSLocationAlwaysAndWhenInUseUsageDescription` entry in your `Info.plist`. Since this permission
24-
> prompt won’t appear to users, you can safely use the same description string as for
25-
> `NSLocationWhenInUseUsageDescription`.
19+
:::info[Background Location Usage Strings]
20+
21+
This Capacitor plugin does not support background geolocation directly. However, it relies on
22+
[`ion-ios-geolocation`](https://github.com/ionic-team/ion-ios-geolocation), which can report
23+
location in the background. As a result, Apple requires you to include a
24+
`NSLocationAlwaysAndWhenInUseUsageDescription` entry in your `Info.plist`. Since this permission
25+
prompt won’t appear to users, you can safely use the same description string as for
26+
`NSLocationWhenInUseUsageDescription`.
27+
28+
:::info
2629

2730
Read about [Configuring `Info.plist`](https://capacitorjs.com/docs/ios/configuration#configuring-infoplist) in the [iOS Guide](https://capacitorjs.com/docs/ios) for more information on setting iOS permissions in Xcode
2831

@@ -39,6 +42,12 @@ This plugin requires the following permissions be added to your `AndroidManifest
3942

4043
The first two permissions ask for location data, both fine and coarse, and the last line is optional but necessary if your app _requires_ GPS to function. You may leave it out, though keep in mind that this may mean your app is installed on devices lacking GPS hardware.
4144

45+
:::note
46+
47+
If you only require approximate location (variable accuracy but usually around 2 kilometers), you may just declare `ACCESS_COARSE_LOCATION` and `<uses-feature`, and use `enableHighAccuracy=false` when requesting location
48+
49+
:::note
50+
4251
Read about [Setting Permissions](https://capacitorjs.com/docs/android/configuration#setting-permissions) in the [Android Guide](https://capacitorjs.com/docs/android) for more information on setting Android permissions.
4352

4453

@@ -247,3 +256,4 @@ The following table list all the plugin errors:
247256
| OS-PLUG-GLOC-0015 | Android | Google Play Services error. |
248257
| OS-PLUG-GLOC-0016 | Android | Location settings error. |
249258
| OS-PLUG-GLOC-0017 | Android | Unable to retrieve location because device has both Network and Location turned off. |
259+
| OS-PLUG-GLOC-0018 | Android | Location permissions are not declared in manifest. Make sure at least ACCESS_COARSE_LOCATION is declared in AndroidManifest.xml, and optionally ACCESS_FINE_LOCATION if you require precise location access. |

android/src/main/kotlin/com/capacitorjs/plugins/geolocation/GeolocationErrors.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,9 @@ object GeolocationErrors {
7373
code = formatErrorCode(17),
7474
message = "Unable to retrieve location because device has both Network and Location turned off."
7575
)
76+
77+
val LOCATION_MANIFEST_PERMISSIONS_MISSING = ErrorInfo(
78+
code = formatErrorCode(18),
79+
message = "Location permissions are not declared in manifest. Make sure at least ACCESS_COARSE_LOCATION is declared in AndroidManifest.xml, and optionally ACCESS_FINE_LOCATION if you require precise location access."
80+
)
7681
}

android/src/main/kotlin/com/capacitorjs/plugins/geolocation/GeolocationPlugin.kt

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import com.getcapacitor.PluginMethod
1111
import com.getcapacitor.annotation.CapacitorPlugin
1212
import com.getcapacitor.annotation.Permission
1313
import com.getcapacitor.annotation.PermissionCallback
14-
import com.google.android.gms.location.LocationServices
1514
import io.ionic.libs.iongeolocationlib.controller.IONGLOCController
1615
import io.ionic.libs.iongeolocationlib.model.IONGLOCException
1716
import io.ionic.libs.iongeolocationlib.model.IONGLOCLocationOptions
@@ -116,7 +115,10 @@ class GeolocationPlugin : Plugin() {
116115
callbackName: String,
117116
onPermissionGranted: () -> Unit
118117
) {
119-
val alias = getAlias(call)
118+
val alias = getAlias(call) ?: run {
119+
call.sendError(GeolocationErrors.LOCATION_MANIFEST_PERMISSIONS_MISSING)
120+
return
121+
}
120122
if (getPermissionState(alias) != PermissionState.GRANTED) {
121123
requestPermissionForAlias(alias, call, callbackName)
122124
} else {
@@ -178,19 +180,20 @@ class GeolocationPlugin : Plugin() {
178180
}
179181

180182
/**
181-
* Gets the appropriate permission alias
183+
* Gets the appropriate permission alias, based on the Android version,
184+
* the permissions declared in the manifest
185+
* and the enableHighAccuracy option provided by the caller.
182186
* @param call the plugin call
183-
* @return String with correct alias
187+
* @return String with correct alias or null if no permissions can be requested
184188
*/
185-
private fun getAlias(call: PluginCall): String {
186-
var alias = LOCATION_ALIAS
187-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
188-
val enableHighAccuracy = call.getBoolean("enableHighAccuracy") ?: false
189-
if (!enableHighAccuracy) {
190-
alias = COARSE_LOCATION_ALIAS
191-
}
192-
}
193-
return alias
189+
private fun getAlias(call: PluginCall): String? {
190+
val hasFine = isPermissionDeclared(LOCATION_ALIAS)
191+
val hasCoarse = isPermissionDeclared(COARSE_LOCATION_ALIAS)
192+
if (!hasFine && !hasCoarse) return null
193+
val enableHighAccuracy = call.getBoolean("enableHighAccuracy") ?: false
194+
val shouldRequestFine =
195+
hasFine && (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || enableHighAccuracy)
196+
return if (shouldRequestFine) LOCATION_ALIAS else COARSE_LOCATION_ALIAS
194197
}
195198

196199
/**
@@ -316,7 +319,7 @@ class GeolocationPlugin : Plugin() {
316319
}
317320

318321
/**
319-
* Extension function to return a unsuccessful plugin result
322+
* Extension function to return an unsuccessful plugin result
320323
* @param error error class representing the error to return, containing a code and message
321324
*/
322325
private fun PluginCall.sendError(error: GeolocationErrors.ErrorInfo) {

example-app/src/js/capacitor-welcome.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Capacitor } from '@capacitor/core';
12
import { SplashScreen } from '@capacitor/splash-screen';
23
import { Geolocation } from '@capacitor/geolocation';
34

@@ -63,6 +64,7 @@ window.customElements.define(
6364
<p>Below are the several features for the capacitor geolocation plugin. You may be prompted to grant location permission to the application.</p>
6465
<button id="check-permission" class="button">Check permission</button>
6566
<button id="request-permission" class="button">Request permission</button>
67+
<button id="request-coarse-permission" class="button" style="display: none;">Request coarse permission</button>
6668
<br><br>
6769
<label for="timeoutInput">timeout: </label>
6870
<input type="number" id="timeoutInput" name="timeoutInput"><br>
@@ -98,13 +100,30 @@ window.customElements.define(
98100
connectedCallback() {
99101
const self = this;
100102

103+
const isAndroid = Capacitor.getPlatform() === 'android';
104+
105+
if (isAndroid) {
106+
self.shadowRoot.querySelector('#request-coarse-permission').style.display = 'inline-block';
107+
}
108+
109+
function permissionStatusToString(permissionStatus) {
110+
if (isAndroid) {
111+
return `Permissions are:\nlocation (fine+coarse) = ${permissionStatus.location}\ncoarseLocation = ${permissionStatus.coarseLocation}`;
112+
}
113+
return `Permissions are:\nlocation = ${permissionStatus.location}`;
114+
}
115+
101116
self.shadowRoot.querySelector('#check-permission').addEventListener('click', async function (e) {
102117
const permissionStatus = await Geolocation.checkPermissions();
103-
alert(`Permissions are:\nlocation = ${permissionStatus.location}`);
118+
alert(permissionStatusToString(permissionStatus));
104119
});
105120
self.shadowRoot.querySelector('#request-permission').addEventListener('click', async function (e) {
106121
const permissionStatus = await Geolocation.requestPermissions();
107-
alert(`Permissions are:\nlocation = ${permissionStatus.location}`);
122+
alert(permissionStatusToString(permissionStatus));
123+
});
124+
self.shadowRoot.querySelector('#request-coarse-permission').addEventListener('click', async function (e) {
125+
const permissionStatus = await Geolocation.requestPermissions({ permissions: ['coarseLocation'] });
126+
alert(permissionStatusToString(permissionStatus));
108127
});
109128

110129
self.shadowRoot.querySelector('#current-location').addEventListener('click', async function (e) {

0 commit comments

Comments
 (0)