I've written some logic in an app that is used to sample the user's location every 15 minutes. I'm only interested on getting a sample of the user's location and not track him continuously.
Once tested on a real device (with locations settings on) I've found out that I only manage getting location while connected to WiFi. But when I'm not connected (say, walking in the street) the FusedLocationApi.getLastLocation function returns the old location sampled when being connected to the WiFi, and never activating the GPS to try and get the real current location.
While I understand I can filter out old locations, I'm not sure how to get the FusedLocationApi to activate the GPS (or at least return an up to date location). Another constraint is that I don't want to leave the GPS on for too long, not to drain the user's battery.
In the code below I connect to GoogleApiClient and try to get the last known location. If this is null, I register to get location updates from it, and as soon as I get one (or the allowed wait time elapses) I disconnect.
I've tested this for a whole day and the FusedLocationApi keeps returning the same old location it had sampled in WiFi in the morning.
/**
* First function in the location collection chain.
* This function is called from outside whenever a client wants to get the user's current location.
*/
public void GetCurrentLocation(Context context, final LocationCallbackable callback) {
currentContext = context;
// Create an instance of GoogleAPIClient.
if (mGoogleApiClient == null) {
mGoogleApiClient = new GoogleApiClient.Builder(context)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
// Add the given callback to the callback list. This will be used to return the location to whoever requested it.
callbacks.add(callback);
// Connect to GoogleApiClient. When connected, this will trigger onConnected and then we'll be able to request the user's location.
mGoogleApiClient.connect();
}
@Override
public void onConnected(@Nullable Bundle bundle) {
// Create a location request.
LocationRequest locationRequest = createLocationRequest();
// Check if we don't have permissions to get fine location.
if (ActivityCompat.checkSelfPermission(currentContext, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// If we no permissions were granted - call the given callback with null.
runCallback(null);
return;
}
// If we got here, we have permissions :)
Location location = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
if (location != null) {
// Disconnect from GoogleApiClient.
disconnect();
// Call the callback that the caller has supplied us with, with the location we have.
runCallback(location);
}
// Location was null - that means the GoogleApiClient didn't have enough time to get the current location.
else {
// In that case we should check if location settings are valid, and if so register to locationUpdated.
// If the settings are not valid, we will terminate.
VerifyLocationSettingsAndContinue(locationRequest);
}
}
/**
* Verify if the device's settings are valid for location sampling.
* If they are - register to get location updates and unregister once we got it.
* If they are not - the sampling chain is terminated, returning null to the client.
*/
private void VerifyLocationSettingsAndContinue(final LocationRequest locationRequest) {
LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder().addLocationRequest(locationRequest);
final LocationManager _this = this;
// Make sure the user's settings are satisfied.
PendingResult<LocationSettingsResult> result = LocationServices.SettingsApi.checkLocationSettings(mGoogleApiClient, builder.build());
// Set a callback for the pending result.
result.setResultCallback(new ResultCallback<LocationSettingsResult>() {
@Override
public void onResult(LocationSettingsResult result) {
final Status status = result.getStatus();
switch (status.getStatusCode()) {
case LocationSettingsStatusCodes.SUCCESS:
case LocationSettingsStatusCodes.RESOLUTION_REQUIRED:
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, locationRequest, _this);
// We can get stuck waiting for an update from FusedLocationApi.requestLocationUpdates.
// To solve this, we wait 10 seconds and terminate our self (if we haven't already finished).
new android.os.Handler().postDelayed(
new Runnable() {
public void run() {
// Only try to stop the operation if we didn't get a location update.
if (!isLocationUpdated) {
stopLocationUpdatesAndDisconnect();
runCallback(null);
}
}
},
10000);
break;
case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE:
default:
// Location settings are not satisfied. Terminate the location sampling by calling the callback with null.
disconnect();
runCallback(null);
break;
}
}
});
}
@Override
public void onLocationChanged(Location location) {
isLocationUpdated = true;
// We got our location sample - unregister ourselves from requesting location updates and disconnect.
stopLocationUpdatesAndDisconnect();
// Call the callback with the location we just got.
runCallback(location);
}
private void stopLocationUpdatesAndDisconnect() {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
disconnect();
}
private LocationRequest createLocationRequest() {
final LocationRequest locationRequest = new LocationRequest();
locationRequest.setInterval(4000);
locationRequest.setFastestInterval(1000);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
// Set that we only want one update.
locationRequest.setNumUpdates(1);
// If we don't get an update, kill the request after 30 seconds.
locationRequest.setExpirationDuration(30000);
return locationRequest;
}