package de.stephanlindauer.criticalmaps.managers;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import org.osmdroid.util.GeoPoint;
import java.util.Date;
import javax.inject.Inject;
import de.stephanlindauer.criticalmaps.App;
import de.stephanlindauer.criticalmaps.events.Events;
import de.stephanlindauer.criticalmaps.model.OwnLocationModel;
import de.stephanlindauer.criticalmaps.prefs.GeoPointPreference;
import de.stephanlindauer.criticalmaps.provider.EventBusProvider;
import de.stephanlindauer.criticalmaps.utils.DateUtils;
import de.stephanlindauer.criticalmaps.utils.LocationUtils;
import de.stephanlindauer.criticalmaps.prefs.SharedPrefsKeys;
import info.metadude.android.typedpreferences.LongPreference;
public class LocationUpdateManager {
private final OwnLocationModel ownLocationModel;
private final EventBusProvider eventService;
//const
private static final float LOCATION_REFRESH_DISTANCE = 20; //20 meters
private static final long LOCATION_REFRESH_TIME = 12 * 1000; //12 seconds
//misc
private LocationManager locationManager;
private SharedPreferences sharedPreferences;
private GeoPointPreference lastKnownLocationPreference;
private LongPreference timeStampPreference;
private boolean isRegisteredForLocationUpdates;
private Location lastPublishedLocation;
private final LocationListener locationListener = new LocationListener() {
@Override
public void onLocationChanged(final Location location) {
if (shouldPublishNewLocation(location)) {
publishNewLocation(location);
lastPublishedLocation = location;
}
}
@Override
public void onStatusChanged(String s, int i, Bundle bundle) {
}
@Override
public void onProviderEnabled(String s) {
}
@Override
public void onProviderDisabled(String s) {
}
};
@Inject
public LocationUpdateManager(App app,
OwnLocationModel ownLocationModel,
EventBusProvider eventService,
SharedPreferences sharedPreferences) {
this.ownLocationModel = ownLocationModel;
this.eventService = eventService;
this.sharedPreferences = sharedPreferences;
locationManager = (LocationManager) app.getSystemService(Context.LOCATION_SERVICE);
}
public void initializeAndStartListening() {
lastKnownLocationPreference = new GeoPointPreference(
sharedPreferences, SharedPrefsKeys.LAST_KNOWN_LOCATION);
timeStampPreference = new LongPreference(
sharedPreferences, SharedPrefsKeys.TIME_STAMP);
registerLocationListeners();
}
private void registerLocationListeners() {
requestLocationUpdatesIfPossible(LocationManager.GPS_PROVIDER);
requestLocationUpdatesIfPossible(LocationManager.NETWORK_PROVIDER);
isRegisteredForLocationUpdates = true;
}
private void requestLocationUpdatesIfPossible(String provider) {
if (locationManager.isProviderEnabled(provider)) {
locationManager.requestLocationUpdates(provider, LOCATION_REFRESH_TIME, LOCATION_REFRESH_DISTANCE, locationListener);
}
}
public void handleShutdown() {
if (!isRegisteredForLocationUpdates) {
return;
}
locationManager.removeUpdates(locationListener);
isRegisteredForLocationUpdates = false;
}
@Nullable
public GeoPoint getLastKnownLocation() {
if (lastKnownLocationPreference == null || timeStampPreference == null) {
return null;
}
if (lastKnownLocationPreference.isSet() && timeStampPreference.isSet()) {
Date timeStampLastCoords = new Date(timeStampPreference.get());
if (DateUtils.isNotLongerAgoThen(timeStampLastCoords, 5, 0)) {
return lastKnownLocationPreference.get();
}
} else {
return LocationUtils.getBestLastKnownLocation(locationManager);
}
return null;
}
private void publishNewLocation(Location location) {
GeoPoint newLocation = new GeoPoint(location.getLatitude(), location.getLongitude());
ownLocationModel.setLocation(newLocation, location.getAccuracy(), location.getTime());
eventService.post(Events.NEW_LOCATION_EVENT);
lastKnownLocationPreference.set(newLocation);
timeStampPreference.set(location.getTime());
}
private boolean shouldPublishNewLocation(Location location) {
// Any location is better than no location
if (lastPublishedLocation == null) {
return true;
}
// Average speed of the CM is ~4 m/s so anything over 30 seconds old, may already
// be well over 120m off. So a newer fix is assumed to be always better.
long timeDelta = location.getTime() - lastPublishedLocation.getTime();
boolean isSignificantlyNewer = timeDelta > OwnLocationModel.MAX_LOCATION_AGE;
boolean isSignificantlyOlder = timeDelta < -OwnLocationModel.MAX_LOCATION_AGE;
boolean isNewer = timeDelta > 0;
if (isSignificantlyNewer) {
return true;
} else if (isSignificantlyOlder) {
return false;
}
int accuracyDelta = (int) (location.getAccuracy() - lastPublishedLocation.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 120;
boolean isFromSameProvider = location.getProvider().equals(lastPublishedLocation.getProvider());
if (isMoreAccurate) {
return true;
} else if (isNewer && !isLessAccurate) {
return true;
} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
return true;
}
return false;
}
}