package net.wigle.wigleandroid.listener;
import static android.location.LocationManager.GPS_PROVIDER;
import static android.location.LocationManager.NETWORK_PROVIDER;
import net.wigle.wigleandroid.ListFragment;
import net.wigle.wigleandroid.MainActivity;
import net.wigle.wigleandroid.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.GpsStatus.Listener;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.widget.Toast;
public class GPSListener implements Listener, LocationListener {
private static final long GPS_TIMEOUT = 15000L;
private static final long NET_LOC_TIMEOUT = 60000L;
private MainActivity mainActivity;
private Location location;
private Location networkLocation;
private GpsStatus gpsStatus;
// set these times to avoid NPE in locationOK() seen by <DooMMasteR>
private Long lastLocationTime = 0L;
private Long lastNetworkLocationTime = 0L;
private Long satCountLowTime = 0L;
private float previousSpeed = 0f;
private LocationListener mapLocationListener;
private int prevStatus = 0;
public GPSListener( MainActivity mainActivity ) {
this.mainActivity = mainActivity;
}
public void setMapListener( LocationListener mapLocationListener ) {
this.mapLocationListener = mapLocationListener;
}
public void setMainActivity( MainActivity mainActivity ) {
this.mainActivity = mainActivity;
}
@Override
public void onGpsStatusChanged( final int event ) {
if ( event == GpsStatus.GPS_EVENT_STOPPED ) {
MainActivity.info("GPS STOPPED");
// this event lies, on one device it gets called when the
// network provider is disabled :( so we do nothing...
// listActivity.setLocationUpdates();
}
// MainActivity.info("GPS event: " + event);
updateLocationData(null);
}
public void handleScanStop() {
MainActivity.info("GPSListener: handleScanStop");
gpsStatus = null;
location = null;
}
@Override
public void onLocationChanged( final Location newLocation ) {
// MainActivity.info("GPS onLocationChanged: " + newLocation);
updateLocationData( newLocation );
if ( mapLocationListener != null ) {
mapLocationListener.onLocationChanged( newLocation );
}
}
@Override
public void onProviderDisabled( final String provider ) {
MainActivity.info("provider disabled: " + provider);
if ( mapLocationListener != null ) {
mapLocationListener.onProviderDisabled( provider );
}
}
@Override
public void onProviderEnabled( final String provider ) {
MainActivity.info("provider enabled: " + provider);
if ( mapLocationListener != null ) {
mapLocationListener.onProviderEnabled( provider );
}
}
@Override
public void onStatusChanged( final String provider, final int status, final Bundle extras ) {
final boolean isgps = "gps".equals(provider);
if (!isgps || status != prevStatus) {
MainActivity.info("provider status changed: " + provider + " status: " + status);
if (isgps) prevStatus = status;
}
if ( mapLocationListener != null ) {
mapLocationListener.onStatusChanged( provider, status, extras );
}
}
/** newLocation can be null */
private void updateLocationData( final Location newLocation ) {
/**
* ALIBI: the location manager call's a non-starter if permission hasn't been granted.
*/
if ( Build.VERSION.SDK_INT >= 23 &&
ContextCompat.checkSelfPermission( mainActivity.getApplicationContext(),
android.Manifest.permission.ACCESS_FINE_LOCATION )
!= PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission( mainActivity.getApplicationContext(),
android.Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
final LocationManager locationManager = (LocationManager)
mainActivity.getSystemService(Context.LOCATION_SERVICE);
// see if we have new data
try {
gpsStatus = locationManager.getGpsStatus(gpsStatus);
} catch (NullPointerException npe) {
MainActivity.error("NPE trying to call getGPSStatus");
return;
}
final int satCount = getSatCount();
boolean newOK = newLocation != null;
final boolean locOK = locationOK( location, satCount );
final long now = System.currentTimeMillis();
if ( newOK ) {
if ( NETWORK_PROVIDER.equals( newLocation.getProvider() ) ) {
// save for later, in case we lose gps
networkLocation = newLocation;
lastNetworkLocationTime = now;
}
else {
lastLocationTime = now;
// make sure there's enough sats on this new gps location
newOK = locationOK( newLocation, satCount );
}
}
if ( mainActivity.inEmulator() && newLocation != null ) {
newOK = true;
}
final boolean netLocOK = locationOK( networkLocation, satCount );
boolean wasProviderChange = false;
if ( ! locOK ) {
if ( newOK ) {
wasProviderChange = true;
//noinspection RedundantIfStatement
if ( location != null && ! location.getProvider().equals( newLocation.getProvider() ) ) {
wasProviderChange = false;
}
location = newLocation;
}
else if ( netLocOK ) {
location = networkLocation;
wasProviderChange = true;
}
else if ( location != null ) {
// transition to null
MainActivity.info( "nulling location: " + location );
location = null;
wasProviderChange = true;
// make sure we're registered for updates
mainActivity.setLocationUpdates();
}
}
else if ( newOK && GPS_PROVIDER.equals( newLocation.getProvider() ) ) {
if ( NETWORK_PROVIDER.equals( location.getProvider() ) ) {
// this is an upgrade from network to gps
wasProviderChange = true;
}
location = newLocation;
if ( wasProviderChange ) {
// save it in prefs
saveLocation();
}
}
else if ( newOK && NETWORK_PROVIDER.equals( newLocation.getProvider() ) ) {
if ( NETWORK_PROVIDER.equals( location.getProvider() ) ) {
// just a new network provided location over an old one
location = newLocation;
}
}
// for maps. so lame!
ListFragment.lameStatic.location = location;
boolean scanScheduled = false;
if ( location != null ) {
final float currentSpeed = location.getSpeed();
if ( (previousSpeed == 0f && currentSpeed > 0f)
|| (previousSpeed < 5f && currentSpeed >= 5f)) {
// moving faster now than before, schedule a scan because the timing config pry changed
MainActivity.info("Going faster, scheduling scan");
mainActivity.scheduleScan();
scanScheduled = true;
}
previousSpeed = currentSpeed;
}
else {
previousSpeed = 0f;
}
// MainActivity.info("sat count: " + satCount);
if ( wasProviderChange ) {
MainActivity.info( "wasProviderChange: satCount: " + satCount
+ " newOK: " + newOK + " locOK: " + locOK + " netLocOK: " + netLocOK
+ (newOK ? " newProvider: " + newLocation.getProvider() : "")
+ (locOK ? " locProvider: " + location.getProvider() : "")
+ " newLocation: " + newLocation );
final SharedPreferences prefs = mainActivity.getSharedPreferences( ListFragment.SHARED_PREFS, 0 );
final boolean disableToast = prefs.getBoolean( ListFragment.PREF_DISABLE_TOAST, false );
if (!disableToast) {
final String announce = location == null ? mainActivity.getString(R.string.lost_location)
: mainActivity.getString(R.string.have_location) + " \"" + location.getProvider() + "\"";
Toast.makeText( mainActivity, announce, Toast.LENGTH_SHORT ).show();
}
final boolean speechGPS = prefs.getBoolean( ListFragment.PREF_SPEECH_GPS, true );
if ( speechGPS ) {
// no quotes or the voice pauses
final String speakAnnounce = location == null ? "Lost Location"
: "Now have location from " + location.getProvider() + ".";
mainActivity.speak( speakAnnounce );
}
if ( ! scanScheduled ) {
// get the ball rolling
MainActivity.info("Location provider change, scheduling scan");
mainActivity.scheduleScan();
}
}
// update the UI
mainActivity.setLocationUI();
}
public void checkLocationOK() {
if ( ! locationOK( location, getSatCount() ) ) {
// do a self-check
updateLocationData(null);
}
}
private boolean locationOK( final Location location, final int satCount ) {
boolean retval = false;
final long now = System.currentTimeMillis();
//noinspection StatementWithEmptyBody
if ( location == null ) {
// bad!
}
else if ( GPS_PROVIDER.equals( location.getProvider() ) ) {
if ( satCount > 0 && satCount < 3 ) {
if ( satCountLowTime == null ) {
satCountLowTime = now;
}
}
else {
// plenty of sats
satCountLowTime = null;
}
boolean gpsLost = satCountLowTime != null && (now - satCountLowTime) > GPS_TIMEOUT;
gpsLost |= now - lastLocationTime > GPS_TIMEOUT;
gpsLost |= horribleGps(location);
retval = ! gpsLost;
}
else if ( NETWORK_PROVIDER.equals( location.getProvider() ) ) {
boolean gpsLost = now - lastNetworkLocationTime > NET_LOC_TIMEOUT;
gpsLost |= horribleGps(location);
retval = ! gpsLost;
}
return retval;
}
private boolean horribleGps(final Location location) {
// try to protect against some horrible gps's out there
// check if accuracy is under 10 miles
boolean horrible = location.hasAccuracy() && location.getAccuracy() > 16000;
horrible |= location.getLatitude() < -90 || location.getLatitude() > 90;
horrible |= location.getLongitude() < -180 || location.getLongitude() > 180;
return horrible;
}
public int getSatCount() {
int satCount = 0;
if ( gpsStatus != null ) {
for ( GpsSatellite sat : gpsStatus.getSatellites() ) {
if ( sat.usedInFix() ) {
satCount++;
}
}
}
return satCount;
}
public void saveLocation() {
// save our location for use on later runs
if ( this.location != null ) {
final SharedPreferences prefs = mainActivity.getSharedPreferences( ListFragment.SHARED_PREFS, 0 );
final Editor edit = prefs.edit();
// there is no putDouble
edit.putFloat( ListFragment.PREF_PREV_LAT, (float) location.getLatitude() );
edit.putFloat( ListFragment.PREF_PREV_LON, (float) location.getLongitude() );
edit.apply();
}
}
public Location getLocation() {
return location;
}
}