24

I am making some tests with the requestLocationUpdates() function from the FusedLocationApi. I am using the PRIORITY_BALANCED_POWER_ACCURACY. A city block precision is fine for me.

When I request the ACCESS_FINE_LOCATION permission, I get around a 100m precision which is great with GPS off. As I do not need a GPS precision but a city block precision, I would like to request only the ACCESS_COARSE_LOCATION permission. However when I request the ACCESS_COARSE_LOCATION permission, I get a 2 km precision. It seems that the device does not use anymore the Wifi permission and only a cell tower precision.

How can I have a better precision with the ACCESS_COARSE_LOCATION permission?

Note: the GPS is disabled on my test device.

Vishwajit Palankar
  • 3,033
  • 3
  • 28
  • 48
poiuytrez
  • 21,330
  • 35
  • 113
  • 172
  • Google builds location model by collecting access points information from users who connect through wifi and cell towers. The more data they collect the better the accuracy so technically you'd need more Android users in your area. On the other hand it sounds odd that GPS could have accuracy in the order of 100m or 2km for access coarse. Are you sure you didn't miscalculate ? – inmyth May 12 '15 at 12:59
  • GPS precision is about the meter. I am doing all my tests with the GPS off. The device uses the wifi when I am using the ACCESS_FINE_LOCATION permission (I have enough android users in my area). I should get the exact same result when using ACCESS_COARSE_LOCATION instead of a 2000m precision. – poiuytrez May 12 '15 at 13:04

2 Answers2

68

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:

enter image description here

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:

enter image description here

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:

enter image description here

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.

Daniel Nugent
  • 43,104
  • 15
  • 109
  • 137
  • 1
    Great tests. However, I think that you are wrong on your last paragraph (before the conclusion). If the user opens up Google Maps and our apps request ACCESS_COARSE_LOCATION, we will (unfortunately) still get 2000m. It seems that the system would like to "hide" a more precise location than 2000m. – poiuytrez May 19 '15 at 11:58
  • @poiuytrez Good point, I made an assumption there. I was thinking that the coordinates would be more precise, regardless of the accuracy reported. I'll test that today, and update with the results! – Daniel Nugent May 19 '15 at 14:01
  • @poiuytrez You were right, the test results show that this functionality does not work with `ACCESS_COARSE_LOCATION`. I updated the answer with test results from today. – Daniel Nugent May 19 '15 at 17:25
  • Thank you for all your tests. We can see clearly that COARSE_LOCATION_PERMISSION does not work like it should be. – poiuytrez May 22 '15 at 11:16
  • Great tests. So in-order to obtain accurate user location we should opt ACCESS_FINE_LOCATION permission. right? – SomeswarReddy Aug 07 '18 at 07:23
  • Great answer, Thanks a lot – Milad Aghamohammadi May 19 '20 at 05:34
4

When you ask for the ACCESS_COARSE_LOCATION permission, the fused location client will give you a city block accuracy, that's the intended behavior and it's written in the documentation. Have a look here under "Specify App Permissions"
What I can suggest is that you use the regular android location provider (not fused location) and try to access the NETWORK provider. It should give you WIFI accuracy.

Ohad Zadok
  • 3,452
  • 1
  • 22
  • 26
  • From the documentation: "Block level accuracy is considered to be about 100 meter accuracy. Using a coarse accuracy such as this often consumes less power.". It should be a 100m accuracy, not 2000m accuracy. – poiuytrez May 13 '15 at 07:23