|
1 |
| -import { Enums, Application, UnhandledErrorEventData, AndroidApplication } from '@nativescript/core'; |
| 1 | +import { Enums, Application, UnhandledErrorEventData, AndroidApplication, Device, ApplicationSettings } from '@nativescript/core'; |
2 | 2 | import { LocationBase, defaultGetLocationTimeout, fastestTimeUpdate, minTimeUpdate } from './common';
|
3 | 3 | import { Options, successCallbackType, errorCallbackType } from '.';
|
4 | 4 | import * as permissions from 'nativescript-permissions';
|
@@ -53,7 +53,7 @@ function errorHandler(errData: UnhandledErrorEventData) {
|
53 | 53 |
|
54 | 54 | export function getCurrentLocation(options: Options): Promise<Location> {
|
55 | 55 | return new Promise(function (resolve, reject) {
|
56 |
| - enableLocationRequest().then(() => { |
| 56 | + enableLocationRequest(false, false).then(() => { |
57 | 57 | if (options.timeout === 0) {
|
58 | 58 | // get last known
|
59 | 59 | LocationManager.getLastLocation(options.maximumAge, resolve, reject);
|
@@ -119,12 +119,23 @@ function _getLocationRequest(options: Options): any {
|
119 | 119 | return mLocationRequest;
|
120 | 120 | }
|
121 | 121 |
|
122 |
| -function _requestLocationPermissions(): Promise<void> { |
| 122 | +function _requestLocationPermissions(always: boolean): Promise<void> { |
123 | 123 | return new Promise<void>(function (resolve, reject) {
|
124 | 124 | if (LocationManager.shouldSkipChecks()) {
|
125 | 125 | resolve();
|
126 | 126 | } else {
|
127 |
| - permissions.requestPermission((<any>android).Manifest.permission.ACCESS_FINE_LOCATION).then(resolve, reject); |
| 127 | + if (always) { |
| 128 | + ApplicationSettings.setBoolean('askedForAlwaysPermission', true); |
| 129 | + permissions |
| 130 | + .requestPermission((<any>android).Manifest.permission.ACCESS_BACKGROUND_LOCATION) |
| 131 | + .then(resolve, reject) |
| 132 | + .catch((e) => { |
| 133 | + console.error('Failed to request Android background location permission due to: ' + e); |
| 134 | + }); |
| 135 | + } else { |
| 136 | + ApplicationSettings.setBoolean('askedForWhileUsePermission', true); |
| 137 | + permissions.requestPermission((<any>android).Manifest.permission.ACCESS_FINE_LOCATION).then(resolve, reject); |
| 138 | + } |
128 | 139 | }
|
129 | 140 | });
|
130 | 141 | }
|
@@ -194,37 +205,48 @@ export function clearWatch(watchId: number): void {
|
194 | 205 | }
|
195 | 206 | }
|
196 | 207 |
|
197 |
| -export function enableLocationRequest(always?: boolean): Promise<void> { |
| 208 | +export function enableLocationRequest(always?: boolean, openSettingsIfLocationHasBeenDenied?: boolean): Promise<void> { |
198 | 209 | return new Promise<void>(function (resolve, reject) {
|
199 |
| - _requestLocationPermissions().then(() => { |
200 |
| - _makeGooglePlayServicesAvailable().then(() => { |
201 |
| - _isLocationServiceEnabled().then( |
202 |
| - () => { |
203 |
| - resolve(); |
204 |
| - }, |
205 |
| - (ex) => { |
206 |
| - if (typeof ex.getStatusCode === 'function') { |
207 |
| - const statusCode = ex.getStatusCode(); |
208 |
| - if (statusCode === com.google.android.gms.location.LocationSettingsStatusCodes.RESOLUTION_REQUIRED) { |
209 |
| - try { |
210 |
| - // cache resolve and reject callbacks in order to call them |
211 |
| - // on REQUEST_ENABLE_LOCATION Activity Result |
212 |
| - _onEnableLocationSuccess = resolve; |
213 |
| - _onEnableLocationFail = reject; |
214 |
| - return ex.startResolutionForResult(Application.android.foregroundActivity || Application.android.startActivity, REQUEST_ENABLE_LOCATION); |
215 |
| - } catch (sendEx) { |
216 |
| - // Ignore the error. |
| 210 | + // on API level <29 there is no ACCESS_BACKGROUND_LOCATION permission |
| 211 | + const _always = Device.sdkVersion >= '29' ? always : false; |
| 212 | + if (!_systemDialogWillShow(_always) && !_permissionIsGiven(_always)) { |
| 213 | + if (openSettingsIfLocationHasBeenDenied) { |
| 214 | + reject(new Error('User needs to enable permission from settings')); |
| 215 | + _goToPhoneSettings(); |
| 216 | + } else { |
| 217 | + reject(new Error('User denied location permission previously')); |
| 218 | + } |
| 219 | + } else { |
| 220 | + _requestLocationPermissions(_always).then(() => { |
| 221 | + _makeGooglePlayServicesAvailable().then(() => { |
| 222 | + _isLocationServiceEnabled().then( |
| 223 | + () => { |
| 224 | + resolve(); |
| 225 | + }, |
| 226 | + (ex) => { |
| 227 | + if (typeof ex.getStatusCode === 'function') { |
| 228 | + const statusCode = ex.getStatusCode(); |
| 229 | + if (statusCode === com.google.android.gms.location.LocationSettingsStatusCodes.RESOLUTION_REQUIRED) { |
| 230 | + try { |
| 231 | + // cache resolve and reject callbacks in order to call them |
| 232 | + // on REQUEST_ENABLE_LOCATION Activity Result |
| 233 | + _onEnableLocationSuccess = resolve; |
| 234 | + _onEnableLocationFail = reject; |
| 235 | + return ex.startResolutionForResult(Application.android.foregroundActivity || Application.android.startActivity, REQUEST_ENABLE_LOCATION); |
| 236 | + } catch (sendEx) { |
| 237 | + // Ignore the error. |
| 238 | + return resolve(); |
| 239 | + } |
| 240 | + } else if (statusCode === com.google.android.gms.location.LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE && isAirplaneModeOn() && isProviderEnabled(android.location.LocationManager.GPS_PROVIDER)) { |
217 | 241 | return resolve();
|
218 | 242 | }
|
219 |
| - } else if (statusCode === com.google.android.gms.location.LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE && isAirplaneModeOn() && isProviderEnabled(android.location.LocationManager.GPS_PROVIDER)) { |
F438
220 |
| - return resolve(); |
221 | 243 | }
|
| 244 | + reject(new Error('Cannot enable the location service. ' + ex)); |
222 | 245 | }
|
223 |
| - reject(new Error('Cannot enable the location service. ' + ex)); |
224 |
| - } |
225 |
| - ); |
| 246 | + ); |
| 247 | + }, reject); |
226 | 248 | }, reject);
|
227 |
| - }, reject); |
| 249 | + } |
228 | 250 | });
|
229 | 251 | }
|
230 | 252 |
|
@@ -274,6 +296,37 @@ function _isLocationServiceEnabled(options?: Options): Promise<boolean> {
|
274 | 296 | });
|
275 | 297 | }
|
276 | 298 |
|
| 299 | +function _goToPhoneSettings() { |
| 300 | + const intent = new android.content.Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, android.net.Uri.fromParts('package', Application.android.context.getPackageName(), null)); |
| 301 | + const activity = Application.android.foregroundActivity || Application.android.startActivity; |
| 302 | + intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK); |
| 303 | + activity.startActivity(intent); |
| 304 | +} |
| 305 | + |
| 306 | +/** |
| 307 | + * The system dialog will not show if we asked for permission previously and the user denied permission with selecting don't ask again (API level <30). |
| 308 | + * Starting on Android 11 (API level 30), there is no 'don't ask again' option. If the user taps Deny for a specific permission more than once during the app's lifetime, |
| 309 | + * the user doesn't see the system permissions dialog if his app requests that permission again. The user's action implies "don't ask again." |
| 310 | + * https://developer.android.com/training/permissions/requesting#one-time |
| 311 | + * With API level 30 Android introduced the 'Only this time' or 'Ask every time' permission which will cause shouldShowRequestPermissionRationale() to return false as if the permission has not been requested yet. |
| 312 | + * This makes it impossible to differentiate between the case where the user granted one time permission and the 'don't ask again' case. Therefore we always assume askedForPermission to be false for API level >= 30 |
| 313 | + * to ensure that the system popup is always shown when the user grants one time permission. |
| 314 | + * https://github.com/Baseflow/flutter-geolocator/issues/662#issuecomment-778121610 |
| 315 | + * @param always if true then check will be done for ACCESS_BACKGROUND_LOCATION permission, if false then check will be done for ACCESS_FINE_LOCATION permission |
| 316 | + * @returns true if the Android permission dialog will be shown to the user when the location permission is requested the next time, returns false otherwise |
| 317 | + */ |
| 318 | +function _systemDialogWillShow(always: boolean): boolean { |
| 319 | + const permissionType = always ? (<any>android).Manifest.permission.ACCESS_BACKGROUND_LOCATION : (<any>android).Manifest.permission.ACCESS_FINE_LOCATION; |
| 320 | + const doNotAskAgain = !androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale(Application.android.foregroundActivity, permissionType); |
| 321 | + let askedForPermission = always ? ApplicationSettings.getBoolean('askedForAlwaysPermission', false) : ApplicationSettings.getBoolean('askedForWhileUsePermission', false); |
| 322 | + askedForPermission = askedForPermission && Device.sdkVersion < '30'; |
| 323 | + return !(askedForPermission && doNotAskAgain); |
| 324 | +} |
| 325 | + |
| 326 | +function _permissionIsGiven(always: boolean): boolean { |
| 327 | + return always ? permissions.hasPermission((<any>android).Manifest.permission.ACCESS_BACKGROUND_LOCATION) : permissions.hasPermission((<any>android).Manifest.permission.ACCESS_FINE_LOCATION); |
| 328 | +} |
| 329 | + |
277 | 330 | export function isEnabled(options?: Options): Promise<boolean> {
|
278 | 331 | return new Promise(function (resolve, reject) {
|
279 | 332 | if (!_isGooglePlayServicesAvailable() || !permissions.hasPermission((<any>android).Manifest.permission.ACCESS_FINE_LOCATION)) {
|
|
0 commit comments