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.
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:
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:
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:
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.