diff options
Diffstat (limited to 'main/src/cgeo/geocaching/GeoDataProvider.java')
| -rw-r--r-- | main/src/cgeo/geocaching/GeoDataProvider.java | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/main/src/cgeo/geocaching/GeoDataProvider.java b/main/src/cgeo/geocaching/GeoDataProvider.java new file mode 100644 index 0000000..58b0696 --- /dev/null +++ b/main/src/cgeo/geocaching/GeoDataProvider.java @@ -0,0 +1,305 @@ +package cgeo.geocaching; + +import cgeo.geocaching.enumerations.LocationProviderType; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.go4cache.Go4Cache; +import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.MemorySubject; + +import android.content.Context; +import android.location.GpsSatellite; +import android.location.GpsStatus; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * Provide information about the user location. This class should be instantiated only once per application. + */ +class GeoDataProvider extends MemorySubject<IGeoData> { + + private static final String LAST_LOCATION_PSEUDO_PROVIDER = "last"; + private final LocationManager geoManager; + private final GpsStatus.Listener gpsStatusListener = new GpsStatusListener(); + private final LocationData gpsLocation = new LocationData(); + private final LocationData netLocation = new LocationData(); + private final Listener networkListener = new Listener(LocationManager.NETWORK_PROVIDER, netLocation); + private final Listener gpsListener = new Listener(LocationManager.GPS_PROVIDER, gpsLocation); + private final Unregisterer unregisterer = new Unregisterer(); + public boolean gpsEnabled = false; + public int satellitesVisible = 0; + public int satellitesFixed = 0; + + private static class LocationData { + public Location location; + public long timestamp = 0; + + public void update(final Location location) { + this.location = location; + timestamp = System.currentTimeMillis(); + } + + public boolean isRecent() { + return isValid() && System.currentTimeMillis() < timestamp + 30000; + } + + public boolean isValid() { + return location != null; + } + } + + private static class GeoData extends Location implements IGeoData { + public boolean gpsEnabled = false; + public int satellitesVisible = 0; + public int satellitesFixed = 0; + + GeoData(final Location location, final boolean gpsEnabled, final int satellitesVisible, final int satellitesFixed) { + super(location); + this.gpsEnabled = gpsEnabled; + this.satellitesVisible = satellitesVisible; + this.satellitesFixed = satellitesFixed; + } + + @Override + public Location getLocation() { + return this; + } + + private static LocationProviderType getLocationProviderType(final String provider) { + if (provider.equals(LocationManager.GPS_PROVIDER)) { + return LocationProviderType.GPS; + } + if (provider.equals(LocationManager.NETWORK_PROVIDER)) { + return LocationProviderType.NETWORK; + } + return LocationProviderType.LAST; + } + + @Override + public LocationProviderType getLocationProvider() { + return getLocationProviderType(getProvider()); + } + + @Override + public Geopoint getCoords() { + return new Geopoint(this); + } + + @Override + public boolean getGpsEnabled() { + return gpsEnabled; + } + + @Override + public int getSatellitesVisible() { + return satellitesVisible; + } + + @Override + public int getSatellitesFixed() { + return satellitesFixed; + } + } + + private class Unregisterer extends Thread { + + private boolean unregisterRequested = false; + private final ArrayBlockingQueue<Boolean> queue = new ArrayBlockingQueue<Boolean>(1); + + public void cancelUnregister() { + try { + queue.put(false); + } catch (final InterruptedException e) { + // Do nothing + } + } + + public void lateUnregister() { + try { + queue.put(true); + } catch (final InterruptedException e) { + // Do nothing + } + } + + @Override + public void run() { + try { + while (true) { + if (unregisterRequested) { + final Boolean element = queue.poll(2500, TimeUnit.MILLISECONDS); + if (element == null) { + // Timeout + unregisterListeners(); + unregisterRequested = false; + } else { + unregisterRequested = element; + } + } else { + unregisterRequested = queue.take(); + } + } + } catch (final InterruptedException e) { + // Do nothing + } + } + + } + + /** + * Build a new geo data provider object. + * <p/> + * There is no need to instantiate more than one such object in an application, as observers can be added + * at will. + * + * @param context the context used to retrieve the system services + */ + GeoDataProvider(final Context context) { + geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + unregisterer.start(); + // Start with an empty GeoData just in case someone queries it before we get + // a chance to get any information. + notifyObservers(new GeoData(new Location(LAST_LOCATION_PSEUDO_PROVIDER), false, 0, 0)); + } + + private void registerListeners() { + geoManager.addGpsStatusListener(gpsStatusListener); + + for (final Listener listener : new Listener[] { networkListener, gpsListener }) { + try { + geoManager.requestLocationUpdates(listener.locationProvider, 0, 0, listener); + } catch (final Exception e) { + Log.w("There is no location provider " + listener.locationProvider); + } + } + } + + private synchronized void unregisterListeners() { + // This method must be synchronized because it will be called asynchronously from the Unregisterer thread. + // We check that no observers have been re-added to prevent a race condition. + if (sizeObservers() == 0) { + geoManager.removeUpdates(networkListener); + geoManager.removeUpdates(gpsListener); + geoManager.removeGpsStatusListener(gpsStatusListener); + } + } + + @Override + protected void onFirstObserver() { + unregisterer.cancelUnregister(); + registerListeners(); + } + + @Override + protected void onLastObserver() { + unregisterer.lateUnregister(); + } + + private class Listener implements LocationListener { + private final String locationProvider; + private final LocationData locationData; + + Listener(final String locationProvider, final LocationData locationData) { + this.locationProvider = locationProvider; + this.locationData = locationData; + } + + @Override + public void onStatusChanged(final String provider, final int status, final Bundle extras) { + // nothing + } + + @Override + public void onProviderDisabled(final String provider) { + // nothing + } + + @Override + public void onProviderEnabled(final String provider) { + // nothing + } + + @Override + public void onLocationChanged(final Location location) { + locationData.update(location); + selectBest(); + } + } + + private final class GpsStatusListener implements GpsStatus.Listener { + + @Override + public void onGpsStatusChanged(final int event) { + boolean changed = false; + switch (event) { + case GpsStatus.GPS_EVENT_SATELLITE_STATUS: { + final GpsStatus status = geoManager.getGpsStatus(null); + int visible = 0; + int fixed = 0; + for (final GpsSatellite satellite : status.getSatellites()) { + if (satellite.usedInFix()) { + fixed++; + } + visible++; + } + if (visible != satellitesVisible || fixed != satellitesFixed) { + satellitesVisible = visible; + satellitesFixed = fixed; + changed = true; + } + break; + } + case GpsStatus.GPS_EVENT_STARTED: + if (!gpsEnabled) { + gpsEnabled = true; + changed = true; + } + break; + case GpsStatus.GPS_EVENT_STOPPED: + if (gpsEnabled) { + gpsEnabled = false; + satellitesFixed = 0; + satellitesVisible = 0; + changed = true; + } + break; + } + + if (changed) { + selectBest(); + } + } + } + + private LocationData best() { + if (gpsLocation.isRecent() || !netLocation.isValid()) { + return gpsLocation.isValid() ? gpsLocation : null; + } + if (!gpsLocation.isValid()) { + return netLocation; + } + return gpsLocation.timestamp > netLocation.timestamp ? gpsLocation : netLocation; + } + + private void selectBest() { + assign(best()); + } + + private void assign(final LocationData locationData) { + if (locationData == null) { + return; + } + + // We do not necessarily get signalled when satellites go to 0/0. + final int visible = gpsLocation.isRecent() ? satellitesVisible : 0; + final IGeoData current = new GeoData(locationData.location, gpsEnabled, visible, satellitesFixed); + notifyObservers(current); + + Go4Cache.signalCoordinates(current.getCoords()); + } + +} |
