package com.jdroid.android.location;
import android.Manifest;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.annotation.RequiresPermission;
import com.jdroid.android.application.AbstractApplication;
import com.jdroid.android.utils.AlarmUtils;
import com.jdroid.java.date.DateUtils;
import com.jdroid.java.utils.LoggerUtils;
import org.slf4j.Logger;
import java.util.List;
public class LocationHelper implements LocationListener {
private static final Logger LOGGER = LoggerUtils.getLogger(LocationHelper.class);
private static final String GPS_TIMEOUT_ACTION = "ACTION_GPS_TIMEOUT";
// 2 minutes
private static final int MAXIMUM_TIME_DELTA = 120000;
private static final LocationHelper INSTANCE = new LocationHelper();
private static final int LOCATION_MIN_TIME = 10000;
private static final int LOCATION_MAX_TIME = 30000;
private Location location;
private Long locationTime;
private Boolean started = false;
private LocationManager locationManager;
public static LocationHelper get() {
return INSTANCE;
}
public LocationHelper() {
// Acquire a reference to the system Location Manager
locationManager = (LocationManager)AbstractApplication.get().getSystemService(Context.LOCATION_SERVICE);
BroadcastReceiver stopLocalizationBroadcastReceiver = new BroadcastReceiver() {
@Override
@RequiresPermission(anyOf = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION})
public void onReceive(Context context, Intent intent) {
stopLocalization();
}
};
AbstractApplication.get().registerReceiver(stopLocalizationBroadcastReceiver,
new IntentFilter(GPS_TIMEOUT_ACTION));
}
public Boolean isLocationEnabled() {
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|| locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
}
/**
* Register the listener with the Location Manager to receive location updates
*/
@RequiresPermission(anyOf = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION})
public synchronized void startLocalization() {
if (!started && hasSignificantlyOlderLocation()) {
started = true;
// get all enabled providers
List<String> enabledProviders = locationManager.getProviders(true);
boolean gpsProviderEnabled = enabledProviders != null && enabledProviders.contains(LocationManager.GPS_PROVIDER);
boolean networkProviderEnabled = enabledProviders != null && enabledProviders.contains(LocationManager.NETWORK_PROVIDER);
if (gpsProviderEnabled || networkProviderEnabled) {
if (gpsProviderEnabled) {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, LOCATION_MIN_TIME, 0, this);
}
if (networkProviderEnabled) {
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, LOCATION_MIN_TIME, 0, this);
}
AlarmUtils.scheduleElapsedRealtimeAlarm(SystemClock.elapsedRealtime() + LOCATION_MAX_TIME,
getCancelPendingIntent());
LOGGER.info("Localization started");
} else {
started = false;
LOGGER.info("All providers disabled");
}
}
}
private PendingIntent getCancelPendingIntent() {
Intent gpsIntent = new Intent(GPS_TIMEOUT_ACTION);
return PendingIntent.getBroadcast(AbstractApplication.get(), 0, gpsIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
}
/**
* Remove the listener to receive location updates
*/
@RequiresPermission(anyOf = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION})
public synchronized void stopLocalization() {
if (started) {
AlarmUtils.cancelAlarm(getCancelPendingIntent());
locationManager.removeUpdates(this);
if (location == null) {
location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (location == null) {
location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
}
locationTime = DateUtils.nowMillis();
}
started = false;
LOGGER.info("Localization stopped");
}
}
/**
* @see android.location.LocationListener#onLocationChanged(android.location.Location)
*/
@Override
public void onLocationChanged(Location location) {
if (isBetterLocation(location, this.location)) {
LOGGER.info("Location changed");
this.location = location;
locationTime = DateUtils.nowMillis();
} else {
LOGGER.info("Location discarded");
}
}
public void onMapLocationChanged(Location location) {
if (location != null) {
LOGGER.info("Location changed");
this.location = location;
locationTime = DateUtils.nowMillis();
} else {
LOGGER.info("Location discarded");
}
}
/**
* @see android.location.LocationListener#onStatusChanged(java.lang.String, int, android.os.Bundle)
*/
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// Do Nothing
}
/**
* @see android.location.LocationListener#onProviderEnabled(java.lang.String)
*/
@Override
public void onProviderEnabled(String provider) {
LOGGER.info("Provider enabled: " + provider);
// Do Nothing
}
/**
* @see android.location.LocationListener#onProviderDisabled(java.lang.String)
*/
@Override
public void onProviderDisabled(String provider) {
LOGGER.info("Provider disabled: " + provider);
// Do Nothing
}
/**
* Determines whether one Location reading is better than the current Location fix
*
* @param location The new Location that you want to evaluate
* @param currentBestLocation The current Location fix, to which you want to compare the new one
*/
protected boolean isBetterLocation(Location location, Location currentBestLocation) {
if (currentBestLocation == null) {
// A new location is always better than no location
return true;
}
// Check whether the new location fix is newer or older
long timeDelta = location.getTime() - currentBestLocation.getTime();
boolean isSignificantlyNewer = timeDelta > MAXIMUM_TIME_DELTA;
boolean isSignificantlyOlder = timeDelta < -MAXIMUM_TIME_DELTA;
boolean isNewer = timeDelta > 0;
// If it's been more than two minutes since the current location, use the new location
// because the user has likely moved
if (isSignificantlyNewer) {
return true;
// If the new location is more than two minutes older, it must be worse
} else if (isSignificantlyOlder) {
return false;
}
// Check whether the new location fix is more or less accurate
int accuracyDelta = (int)(location.getAccuracy() - currentBestLocation.getAccuracy());
boolean isLessAccurate = accuracyDelta > 0;
boolean isMoreAccurate = accuracyDelta < 0;
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
// Check if the old and new location are from the same provider
boolean isFromSameProvider = isSameProvider(location.getProvider(), currentBestLocation.getProvider());
// Determine location quality using a combination of timeliness and accuracy
if (isMoreAccurate) {
return true;
} else if (isNewer && !isLessAccurate) {
return true;
} else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
return true;
}
return false;
}
/**
* Checks whether two providers are the same
*/
private boolean isSameProvider(String provider1, String provider2) {
if (provider1 == null) {
return provider2 == null;
}
return provider1.equals(provider2);
}
/**
* @return the location
*/
public Location getLocation() {
return location;
}
public Boolean hasLocation() {
return location != null;
}
public Boolean hasSignificantlyOlderLocation() {
if (location != null) {
long timeDelta = DateUtils.nowMillis() - locationTime;
return timeDelta > MAXIMUM_TIME_DELTA;
}
return true;
}
public Double getLatitude() {
return location != null ? location.getLatitude() : null;
}
public Double getLongitude() {
return location != null ? location.getLongitude() : null;
}
}