Skip to content

Commit

Permalink
fix(android): request ACCESS_COARSE_LOCATION permission (#12884)
Browse files Browse the repository at this point in the history
* fix(android): request ACCESS_COARSE_LOCATION permission

* chore(android): improve coarse location permission handling

* fix(android): update getLocationServicesEnabled()

* fix(android): handle absence of ACCESS_FINE_LOCATION on Android 12

* fix(android): validate location permissions

* fix(android): onLocationChanged() null check

Co-authored-by: Joshua Quick <jquick@axway.com>
Co-authored-by: Ewan Harris <ewanharris93@gmail.com>
Co-authored-by: Lokesh Choudhary <lchoudhary@axway.com>
  • Loading branch information
4 people authored Aug 31, 2021
1 parent 5addc5e commit 30f66e9
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,15 @@
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Build;
import android.os.Handler;
import android.os.Message;

import androidx.annotation.NonNull;
import androidx.core.location.LocationManagerCompat;

/**
* GeolocationModule exposes all common methods and properties relating to geolocation behavior
* associated with Ti.Geolocation to the Titanium developer. Only cross platform API points should
Expand Down Expand Up @@ -144,6 +148,7 @@ public class GeolocationModule extends KrollModule implements Handler.Callback,

private FusedLocationProvider fusedLocationProvider;
private Geocoder geocoder;
private LocationManager locationManager;

/**
* Constructor
Expand All @@ -156,6 +161,7 @@ public GeolocationModule()

fusedLocationProvider = new FusedLocationProvider(context, this);
geocoder = new Geocoder(context);
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);

tiLocation = new TiLocation();
tiCompass = new TiCompass(this, tiLocation);
Expand Down Expand Up @@ -206,6 +212,9 @@ public boolean handleMessage(Message message)
*/
public void onLocationChanged(Location location)
{
if (location == null) {
return;
}
lastLocation = location;

// Execute getCurrentPosition() callbacks/Promises
Expand Down Expand Up @@ -497,12 +506,11 @@ public boolean hasLocationPermissions()
if (Build.VERSION.SDK_INT < 23) {
return true;
}
Context context = TiApplication.getInstance().getApplicationContext();
if (context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
return true;
}
return false;

Context context = TiApplication.getInstance();
int result = context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
result &= context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION);
return (result == PackageManager.PERMISSION_GRANTED);
}

@SuppressLint("NewApi")
Expand All @@ -512,14 +520,15 @@ public KrollPromise<KrollDict> requestLocationPermissions(@Kroll.argument(option
{
final KrollObject callbackThisObject = getKrollObject();
return KrollPromise.create((promise) -> {
// Fetch the optional callback argument.
KrollFunction permissionCB;
if (type instanceof KrollFunction && permissionCallback == null) {
permissionCB = (KrollFunction) type;
} else {
permissionCB = permissionCallback;
}

// already have permissions, fall through
// Do not continue if we already have permission.
if (hasLocationPermissions()) {
KrollDict response = new KrollDict();
response.putCodeAndMessage(0, null);
Expand All @@ -530,11 +539,72 @@ public KrollPromise<KrollDict> requestLocationPermissions(@Kroll.argument(option
return;
}

TiBaseActivity.registerPermissionRequestCallback(TiC.PERMISSION_CODE_LOCATION, permissionCB,
callbackThisObject, promise);
Activity currentActivity = TiApplication.getInstance().getCurrentActivity();
currentActivity.requestPermissions(new String[] { Manifest.permission.ACCESS_FINE_LOCATION },
TiC.PERMISSION_CODE_LOCATION);
// Do not continue if there is no activity to host the request dialog.
Activity activity = TiApplication.getInstance().getCurrentActivity();
if (activity == null) {
KrollDict response =
buildLocationErrorEvent(-1, "There are no activities to host the location request dialog.");
if (permissionCB != null) {
permissionCB.callAsync(callbackThisObject, response);
}
promise.reject(new Throwable(response.getString(TiC.EVENT_PROPERTY_ERROR)));
return;
}

// Set up a custom callback to handle the user's grant/denial of this permission.
TiBaseActivity.OnRequestPermissionsResultCallback activityCallback;
activityCallback = new TiBaseActivity.OnRequestPermissionsResultCallback() {
@Override
public void onRequestPermissionsResult(
@NonNull TiBaseActivity activity, int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults)
{
// Unregister this callback.
TiBaseActivity.unregisterPermissionRequestCallback(TiC.PERMISSION_CODE_LOCATION);

// Do not continue if there is no callback to invoke with the result.
if ((permissionCB) == null && (promise == null)) {
return;
}

// Check if at least 1 location permission has been granted.
// Note: As of Android 12, COARSE permission can be granted while FINE is denied.
boolean wasGranted = false;
if (permissions.length == grantResults.length) {
for (int index = 0; index < permissions.length; index++) {
switch (permissions[index]) {
case Manifest.permission.ACCESS_COARSE_LOCATION:
case Manifest.permission.ACCESS_FINE_LOCATION:
wasGranted = (grantResults[index] == PackageManager.PERMISSION_GRANTED);
break;
}
if (wasGranted) {
break;
}
}
}

// Invoke callback(s) with the result.
KrollDict response = new KrollDict();
if (wasGranted) {
response.putCodeAndMessage(0, null);
promise.resolve(response);
} else {
response.putCodeAndMessage(-1, "Location permission denied.");
promise.reject(response);
}
if (permissionCB != null) {
permissionCB.callAsync(callbackThisObject, response);
}
}
};

// Prompt end-user for permission.
// Note: As of Android 12, we cannot request FINE permission by itself. We must also include COARSE.
TiBaseActivity.registerPermissionRequestCallback(TiC.PERMISSION_CODE_LOCATION, activityCallback);
activity.requestPermissions(
new String[] { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION },
TiC.PERMISSION_CODE_LOCATION);
});
}

Expand Down Expand Up @@ -628,6 +698,17 @@ private void doEnableLocationProviders(HashMap<String, LocationProviderProxy> lo
LocationProviderProxy locationProvider = locationProviders.get(iterator.next());
registerLocationProvider(locationProvider);
}

// On Android 12+, check for ACCESS_FINE_LOCATION.
// If ACCESS_FINE_LOCATION is denied, return last known location.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && hasLocationPermissions()) {
Context context = TiApplication.getInstance();
int result = context.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);

if (result == PackageManager.PERMISSION_DENIED) {
onLocationChanged(tiLocation.getLastKnownLocation());
}
}
}
}

Expand Down Expand Up @@ -660,7 +741,7 @@ private void disableLocationProviders()
@Kroll.getProperty
public boolean getLocationServicesEnabled()
{
return tiLocation.getLocationServicesEnabled();
return LocationManagerCompat.isLocationEnabled(locationManager);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,6 @@ public boolean isProvider(String name)
return knownProviders.contains(name);
}

public boolean getLocationServicesEnabled()
{
// Fetch all enabled location providers.
List<String> providerNames = locationManager.getProviders(true);
if ((providerNames == null) || (providerNames.size() <= 0)) {
return false;
}

// Log all providers currently enabled.
if (Log.isDebugModeEnabled()) {
Log.i(TAG, "Enabled location provider count: " + providerNames.size());
for (String providerName : providerNames) {
Log.i(TAG, providerName + " service available");
}
}

// Only return true if location can be obtained via GPS or WiFi/Cellular.
// Ignore "passive" provider and "test" providers.
boolean isEnabled = providerNames.contains(LocationManager.GPS_PROVIDER);
isEnabled |= providerNames.contains(LocationManager.NETWORK_PROVIDER);
return isEnabled;
}

@SuppressLint("MissingPermission")
public Location getLastKnownLocation()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,38 +82,49 @@ public void onGeolocationPermissionsShowPrompt(
return;
}

// Prompt end-user for permission on Android 6.0 and higher if needed.
// Prompt end-user for FINE location permission on Android 6.0 and higher if needed.
// Note: As of Android 12, we must also request the COARSE permission (will fail without it),
// but ignore COARSE permission since WebView location access requires FINE.
if (Build.VERSION.SDK_INT >= 23) {
int permissionResult = activity.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION);
if (permissionResult != PackageManager.PERMISSION_GRANTED) {
TiBaseActivity.OnRequestPermissionsResultCallback activityCallback;
activityCallback = new TiBaseActivity.OnRequestPermissionsResultCallback() {
@Override
public void onRequestPermissionsResult(
@NonNull TiBaseActivity activity, int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults)
{
// Unregister this callback.
TiBaseActivity.unregisterPermissionRequestCallback(TiC.PERMISSION_CODE_LOCATION);

// Determine if location permission was granted.
boolean wasGranted = false;
if (permissions.length == grantResults.length) {
for (int index = 0; index < permissions.length; index++) {
if (Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[index])) {
wasGranted = (grantResults[index] == PackageManager.PERMISSION_GRANTED);
break;
if (activity.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
// System won't prompt for permission since user already denied it. So, immediately fail it.
callback.invoke(origin, false, false);
} else {
// Prompt end-user for permission.
TiBaseActivity.OnRequestPermissionsResultCallback activityCallback;
activityCallback = new TiBaseActivity.OnRequestPermissionsResultCallback() {
@Override
public void onRequestPermissionsResult(
@NonNull TiBaseActivity activity, int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults)
{
// Unregister this callback.
TiBaseActivity.unregisterPermissionRequestCallback(TiC.PERMISSION_CODE_LOCATION);

// Determine if FINE location permission was granted. (Ignore COARSE permission.)
boolean granted = false;
if (permissions.length == grantResults.length) {
for (int index = 0; index < permissions.length; index++) {
if (Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[index])) {
granted = (grantResults[index] == PackageManager.PERMISSION_GRANTED);
break;
}
}
}
}

// Notify WebView whether or not location access was granted.
callback.invoke(origin, wasGranted, false);
}
};
TiBaseActivity.registerPermissionRequestCallback(TiC.PERMISSION_CODE_LOCATION, activityCallback);
String[] permissions = new String[] { Manifest.permission.ACCESS_FINE_LOCATION };
activity.requestPermissions(permissions, TiC.PERMISSION_CODE_LOCATION);
// Notify WebView whether or not location access was granted.
callback.invoke(origin, granted, false);
}
};
TiBaseActivity.registerPermissionRequestCallback(TiC.PERMISSION_CODE_LOCATION, activityCallback);
String[] permissions = new String[] {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
};
activity.requestPermissions(permissions, TiC.PERMISSION_CODE_LOCATION);
}
return;
}
}
Expand Down

0 comments on commit 30f66e9

Please sign in to comment.