Access_Coarse_Location Permission Gives a Cell Tower Precision on Android

ACCESS_COARSE_LOCATION permission gives a cell tower precision on Android

This is an interesting problem, and I was under the impression that using ACCESS_COARSE_LOCATION would use WiFi, since that's what the documentation says.

The documentation for ACCESS_COARSE_LOCATION states:

Allows an app to access approximate location derived from network
location sources such as cell towers and Wi-Fi.

So, I put it to the test, and the results are surprising.

Here is the code that I used to test with:

public class MainActivity extends Activity implements
GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {

LocationRequest mLocationRequest;
GoogleApiClient mGoogleApiClient;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

buildGoogleApiClient();
mGoogleApiClient.connect();
}

@Override
protected void onPause(){
super.onPause();
if (mGoogleApiClient != null) {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
}
}

protected synchronized void buildGoogleApiClient() {
Toast.makeText(this,"buildGoogleApiClient",Toast.LENGTH_SHORT).show();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}

@Override
public void onConnected(Bundle bundle) {
Toast.makeText(this,"onConnected",Toast.LENGTH_SHORT).show();

mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(10);
mLocationRequest.setFastestInterval(10);
mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
//mLocationRequest.setPriority(LocationRequest.PRIORITY_LOW_POWER);
//mLocationRequest.setSmallestDisplacement(0.1F);

LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
}

@Override
public void onConnectionSuspended(int i) {
Toast.makeText(this,"onConnectionSuspended",Toast.LENGTH_SHORT).show();
}

@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Toast.makeText(this,"onConnectionFailed",Toast.LENGTH_SHORT).show();
}

@Override
public void onLocationChanged(Location location) {

Log.d("locationtesting", "accuracy: " + location.getAccuracy() + " lat: " + location.getLatitude() + " lon: " + location.getLongitude());

Toast.makeText(this,"Location Changed",Toast.LENGTH_SHORT).show();
}
}

AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

build.gradle:

compile 'com.google.android.gms:play-services:7.3.0'

The first test I did was with PRIORITY_BALANCED_POWER_ACCURACY, and no WiFi. Note that I also disabled Always Allow Scanning, since it states:

Let Google Location Service and other applications scan for Wi-Fi
networks, even when Wi-Fi is off

So, that would certainly skew the results if it was enabled.

Note that I also had Location Mode set in Battery Saving Mode for all tests, so the GPS radio was off the entire time.

Here are the results of PRIORITY_BALANCED_POWER_ACCURACY, ACCESS_COARSE_LOCATION, and no WiFi:

accuracy: 2000.0 lat: 37.78378378378378 lon: -122.40320943772021

So, it says 2000 meter accuracy, and here is how far away the actual coordinates are, the green arrow shows where I actually am:

Sample Image

Then, I enabled WiFi, and ran the test again, and surprisingly, the results were exactly the same!

accuracy: 2000.0 lat: 37.78378378378378 lon: -122.40320943772021

Then, I switched to LocationRequest.PRIORITY_LOW_POWER in the LocationRequest while keeping android.permission.ACCESS_COARSE_LOCATION in the AndroidManifest.xml.

No WiFi:

accuracy: 2000.0 lat: 37.78378378378378 lon: -122.40320943772021

With WiFi:

accuracy: 2000.0 lat: 37.78378378378378 lon: -122.40320943772021

The results were exactly the same again!
Using PRIORITY_LOW_POWER had the same results as using PRIORITY_BALANCED_POWER_ACCURACY, in that the WiFi state did not seem to have any effect on accuracy of coordinates.

Then, just to cover all the bases, I changed back to LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY, and switched the AndroidManifest.xml to ACCESS_FINE_LOCATION :

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

First test, no WiFi:

accuracy: 826.0 lat: 37.7825458 lon: -122.3948752

So, it says accuracy of 826 meters, and here is how close it was on the map:

Sample Image

Then, I powered on WiFi, and here is the result:

accuracy: 18.847 lat: 37.779679 lon: -122.3930918

It's literally spot on, as you can see on the map:

Sample Image

It seems that it matters less what you use in your LocationRequest in the Java code, and more what permission you use in the AndroidManifest.xml, since the results here clearly show that when using ACCESS_FINE_LOCATION, having the WiFi radio on or off made a huge difference in the accuracy, and it was also more accurate in general.

It certainly seems as though the documentation is a bit miss-leading, and that while using android.permission.ACCESS_COARSE_LOCATION, having the WiFi radio on or off doesn't make a difference when your app is the only one making location requests.

Another thing that the documentation states is that using PRIORITY_BALANCED_POWER_ACCURACY will let your app "piggy-back" onto location requests made by other apps.
From the documentation:

They will only be assigned power blame for the interval set by
setInterval(long), but can still receive locations triggered by other
applications
at a rate up to setFastestInterval(long).

So if the user opens up Google Maps, according to the documentation your app can obtain a more accurate location at that point. That is one of the major up-sides of using the new Fused Location Provider rather than the older APIs, since it decreases the amount of battery drain of your app without much work on your part.

Edit: I performed a test of this functionality, to see what would happen while using ACCESS_COARSE_LOCATION.

First Test: ACCESS_COARSE_LOCATION, PRIORITY_BALANCED_POWER_ACCURACY, and WiFi on:

accuracy: 2000.0 lat: 37.78378378378378 lon: -122.38041129850662

That placed me out in the water, quite far from my current location.
Then, I exited the test app, launched Google Maps, which located me exactly where I am, then re-launched the test app.
The test app was not able to piggy-back onto the location from Google Maps, and the result was exactly the same as before!

accuracy: 2000.0 lat: 37.78378378378378 lon: -122.38041129850662

I re-tested this a few times, just to be sure, but it really looks like using ACCESS_COARSE_LOCATION also disables the ability of apps to "piggy-back" on to locations obtained by other apps.

It looks like using ACCESS_COARSE_LOCATION in the AndroidManifest.xml really cripples the app in terms of getting precise location data.

In conclusion, the only thing you can really do is hone in on the best combination of settings that work for you and your app, and hopefully the results of this test can help you make that decision.

Will Android still use GPS to pull location when only COARSE_LOCATION is requested?

I think this other post and the Android documentation it references answers this question. The Android doc says the following.

Note: If you are using both NETWORK_PROVIDER and GPS_PROVIDER, then you need to request only the ACCESS_FINE_LOCATION permission, because it includes permission for both providers. (Permission for ACCESS_COARSE_LOCATION includes permission only for NETWORK_PROVIDER.)

So, if I'm reading that right, requesting ACCESS_COARSE_LOCATION can't pull from GPS because it does not request access for the GPS_PROVIDER.

Pressing allows after requesting permission for ACCESS_COARSE_LOCATION doesn't turn on location - nougat

Location can be determined by two ways:

1.Using NETWORK_PROVIDER

2.Using GPS_PROVIDER

NETWORK_PROVIDER: It determines the location of the users using cell towers,wifi access points. It is commonly used for determining location inside the rooms or buildings. Here the GPS coordinates are not able to be obtained.

You can specify either

<uses-permission android:name=”android.permission.ACCESS_COARSE_LOCATION” />

or

<uses-permission android:name=”android.permission.ACCESS_FINE_LOCATION” />

in order to get location using the NETWORK_PROVIDER.

GPS_PROVIDER:
It determines the location of the users using satellites. For this, the GPS coordinates are obtained and used for positioning. The GPS receiver in the smartphone receives the signals from satellites. These signals are processed and precise locations are determined.It works better in outdoors – direct sky/satellite views and communication occurs.

You need specify the permission

<uses-permission android:name=”android.permission.ACCESS_FINE_LOCATION” />

in order to use location from GPS_PROVIDER.

Fine locations:
It gives better and accurate locations. So, that I recommend you to use this to get your beacon locations. It gives permission for using both GPS_PROVIDER and NETWORK_PROVIDER or GPS_PROVIDER only for determining the position.

Coarse locations:
It provides less accurate locations.It gives permission for using NETWORK_PROVIDER only for determining the position.

Now, come to the implementation.
- Declare the above said two permissions in the AnroidManifest.xml file:
- In the java part, do the following:
Request the permission if it not granted yet:

private void requestPermission(Activity activity) {
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.CALL_PHONE}, MainActivity.PERMISSION_REQUEST_CODE_LOCATION);
}

When the above method is called, a dialog asking permission will appear. On selecting Allow or Deny, the below callback gets triggered.

In the onRequestPermissionsResult

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case PERMISSION_REQUEST_CODE_LOCATION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
boolean isGpsProviderEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
boolean isNetworkProviderEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
//Location permission is given. Check if the providers are available and start location updates.
if (isGpsProviderEnabled && isNetworkProviderEnabled) {
startLocationUpdates();
} else {
Log.d(TAG, "GPS and Network providers are disabled");
}
} else if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_DENIED) {
boolean should = ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION);
if (should) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, MainActivity.PERMISSION_REQUEST_CODE_LOCATION);
} else {
promptSettings();
}
}
}
}

In the promptSettings() method, let the user to enable location from the Settings screen.

private void promptSettings() {
AlertDialog.Builder builder;

builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle(getResources().getString(R.string.unable_to_find_location));
builder.setMessage(getResources().getString(R.string.message_denied_location_permission));
builder.setCancelable(false);
builder.setPositiveButton(getResources().getString(R.string.go_to_settings), (dialog, which) -> {
dialog.dismiss();
builder = null;
if (!checkPermission(MainActivity.this)) {
goToSettings();
}
});
builder.show();
}

In the check permissions method:

public boolean checkPermission(Context context) {
int result = ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION);
return result == PackageManager.PERMISSION_GRANTED;
}

The goToSettings() allows the user to go to Settings screen:

private void goToSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, 1);
}

Note: You need to give the below permissions in the manifest to scan the beacons. I hope you are doing that, if not please do it.

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

Android location permissions

The main permissions you need are android.permission.ACCESS_COARSE_LOCATION or android.permission.ACCESS_FINE_LOCATION.

Only fine location will allow you access to gps data, and allows you access to everything else coarse location gives. You can use the methods of the LocationManager to acquire location data from gps and cell tower sources already, you do not have to work out this information yourself.

If you are targeting API Level 21 (5.0) or higher, you may also need this:

<uses-feature android:name="android.hardware.location.gps" />

Is ACCESS_COARSE_LOCATION necessary if the user has already granted ACCESS_FINE_LOCATION?

Sort of already answered here.

If I have ACCESS_FINE_LOCATION already, can I omit ACCESS_COARSE_LOCATION?

Some additional information, you should look at permission group.

https://developer.android.com/preview/features/runtime-permissions.html

They are in the same permission group. If you really want to play safe, just include both in your manifest, and request them on need. It would be transparent to user.



Related Topics



Leave a reply



Submit