diff options
Diffstat (limited to 'main/src/cgeo/geocaching/sensors')
| -rw-r--r-- | main/src/cgeo/geocaching/sensors/DirectionProvider.java | 146 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/sensors/GeoData.java | 87 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/sensors/GeoDataProvider.java | 255 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/sensors/GeoDirHandler.java | 11 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/sensors/GpsStatusProvider.java | 99 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/sensors/IGeoData.java | 5 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/sensors/OrientationProvider.java | 67 | ||||
| -rw-r--r-- | main/src/cgeo/geocaching/sensors/RotationProvider.java | 83 |
8 files changed, 357 insertions, 396 deletions
diff --git a/main/src/cgeo/geocaching/sensors/DirectionProvider.java b/main/src/cgeo/geocaching/sensors/DirectionProvider.java deleted file mode 100644 index ed5d76a..0000000 --- a/main/src/cgeo/geocaching/sensors/DirectionProvider.java +++ /dev/null @@ -1,146 +0,0 @@ -package cgeo.geocaching.sensors; - -import cgeo.geocaching.CgeoApplication; -import cgeo.geocaching.utils.AngleUtils; -import cgeo.geocaching.utils.StartableHandlerThread; - -import rx.Observable; -import rx.Observable.OnSubscribe; -import rx.Subscriber; -import rx.subjects.BehaviorSubject; - -import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.os.Handler; -import android.os.Process; -import android.view.Surface; -import android.view.WindowManager; - -public class DirectionProvider { - - private static final BehaviorSubject<Float> SUBJECT = BehaviorSubject.create(0.0f); - - private static final WindowManager WINDOW_MANAGER = (WindowManager) CgeoApplication.getInstance().getSystemService(Context.WINDOW_SERVICE); - - private DirectionProvider() { - // utility class - } - - static class Listener implements SensorEventListener, StartableHandlerThread.Callback { - - private int count = 0; - - private SensorManager sensorManager; - - @Override - public void onSensorChanged(final SensorEvent event) { - SUBJECT.onNext(event.values[0]); - } - - @Override - public void onAccuracyChanged(final Sensor sensor, final int accuracy) { - /* - * There is a bug in Android, which apparently causes this method to be called every - * time the sensor _value_ changed, even if the _accuracy_ did not change. Do not have any code in here. - * - * See for example https://code.google.com/p/android/issues/detail?id=14792 - */ - } - - @Override - public void start(final Context context, final Handler handler) { - if (!hasSensor(context)) { - return; - } - if (++count == 1) { - Sensor orientationSensor = getOrientationSensor(context); - sensorManager.registerListener(this, orientationSensor, SensorManager.SENSOR_DELAY_NORMAL, handler); - } - } - - @Override - public void stop() { - if (!hasSensor) { - return; - } - if (--count == 0) { - sensorManager.unregisterListener(this); - } - } - - /** - * Assume that there is an orientation sensor, unless we have really checked that - */ - private boolean hasSensor = true; - - /** - * Flag for one time check if there is a sensor. - */ - private boolean hasSensorChecked = false; - - public boolean hasSensor(Context context) { - if (!hasSensorChecked) { - hasSensor = getOrientationSensor(context) != null; - hasSensorChecked = true; - } - return hasSensor; - } - - // This will be removed when using a new location service. Until then, it is okay to be used. - @SuppressWarnings("deprecation") - private Sensor getOrientationSensor(final Context context) { - sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - return sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); - } - - } - - private static final StartableHandlerThread HANDLER_THREAD = - new StartableHandlerThread("DirectionProvider thread", Process.THREAD_PRIORITY_BACKGROUND, new Listener()); - - static { - HANDLER_THREAD.start(); - } - - public static Observable<Float> create(final Context context) { - return Observable.create(new OnSubscribe<Float>() { - @Override - public void call(final Subscriber<? super Float> subscriber) { - HANDLER_THREAD.start(subscriber, context); - SUBJECT.subscribe(subscriber); - } - }); - } - - /** - * Take the phone rotation (through a given activity) in account and adjust the direction. - * - * @param direction the unadjusted direction in degrees, in the [0, 360[ range - * @return the adjusted direction in degrees, in the [0, 360[ range - */ - - public static float getDirectionNow(final float direction) { - return AngleUtils.normalize(direction + getRotationOffset()); - } - - static float reverseDirectionNow(final float direction) { - return AngleUtils.normalize(direction - getRotationOffset()); - } - - private static int getRotationOffset() { - switch (WINDOW_MANAGER.getDefaultDisplay().getRotation()) { - case Surface.ROTATION_90: - return 90; - case Surface.ROTATION_180: - return 180; - case Surface.ROTATION_270: - return 270; - default: - return 0; - } - } - -} diff --git a/main/src/cgeo/geocaching/sensors/GeoData.java b/main/src/cgeo/geocaching/sensors/GeoData.java index c0b3974..561c09f 100644 --- a/main/src/cgeo/geocaching/sensors/GeoData.java +++ b/main/src/cgeo/geocaching/sensors/GeoData.java @@ -2,22 +2,42 @@ package cgeo.geocaching.sensors; import cgeo.geocaching.enumerations.LocationProviderType; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.utils.Log; +import android.content.Context; import android.location.Location; import android.location.LocationManager; -class GeoData extends Location implements IGeoData { - private final boolean gpsEnabled; - private final int satellitesVisible; - private final int satellitesFixed; - private final boolean pseudoLocation; +import javax.annotation.Nullable; - GeoData(final Location location, final boolean gpsEnabled, final int satellitesVisible, final int satellitesFixed, final boolean pseudoLocation) { +public class GeoData extends Location implements IGeoData { + + public static final String INITIAL_PROVIDER = "initial"; + public static final String FUSED_PROVIDER = "fused"; + public static final String LOW_POWER_PROVIDER = "low-power"; + + // Some devices will not have the last position available (for example the emulator). In this case, + // rather than waiting forever for a position update which might never come, we emulate it by placing + // the user arbitrarly at Paris Notre-Dame, one of the most visited free tourist attractions in the world. + final public static GeoData DUMMY_LOCATION = new GeoData(new Location(INITIAL_PROVIDER)); + static { + DUMMY_LOCATION.setLatitude(48.85308); + DUMMY_LOCATION.setLongitude(2.34962); + } + + public GeoData(final Location location) { super(location); - this.gpsEnabled = gpsEnabled; - this.satellitesVisible = satellitesVisible; - this.satellitesFixed = satellitesFixed; - this.pseudoLocation = pseudoLocation; + } + + @Nullable + static Location best(@Nullable final Location gpsLocation, @Nullable final Location netLocation) { + if (isRecent(gpsLocation) || !(netLocation != null)) { + return gpsLocation; + } + if (!(gpsLocation != null)) { + return netLocation; + } + return gpsLocation.getTime() >= netLocation.getTime() ? gpsLocation : netLocation; } @Override @@ -32,6 +52,13 @@ class GeoData extends Location implements IGeoData { if (provider.equals(LocationManager.NETWORK_PROVIDER)) { return LocationProviderType.NETWORK; } + // LocationManager.FUSED_PROVIDER constant is not available at API level 9 + if (provider.equals(FUSED_PROVIDER)) { + return LocationProviderType.FUSED; + } + if (provider.equals(LOW_POWER_PROVIDER)) { + return LocationProviderType.LOW_POWER; + } return LocationProviderType.LAST; } @@ -45,23 +72,35 @@ class GeoData extends Location implements IGeoData { return new Geopoint(this); } - @Override - public boolean getGpsEnabled() { - return gpsEnabled; + @Nullable public static GeoData getInitialLocation(final Context context) { + final LocationManager geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + if (geoManager == null) { + Log.w("No LocationManager available"); + return null; + } + try { + // Try to find a sensible initial location from the last locations known to Android. + final Location lastGpsLocation = geoManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); + final Location lastNetworkLocation = geoManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + final Location bestLocation = best(lastGpsLocation, lastNetworkLocation); + if (bestLocation != null) { + bestLocation.setProvider(INITIAL_PROVIDER); + return new GeoData(bestLocation); + } + Log.i("No last known location available"); + return null; + } catch (final Exception e) { + // This error is non-fatal as its only consequence is that we will start with a dummy location + // instead of a previously known one. + Log.e("Error when retrieving last known location", e); + return null; + } } - @Override - public int getSatellitesVisible() { - return satellitesVisible; - } - @Override - public int getSatellitesFixed() { - return satellitesFixed; - } - @Override - public boolean isPseudoLocation() { - return pseudoLocation; + public static boolean isRecent(@Nullable final Location location) { + return location != null && System.currentTimeMillis() <= location.getTime() + 30000; } + } diff --git a/main/src/cgeo/geocaching/sensors/GeoDataProvider.java b/main/src/cgeo/geocaching/sensors/GeoDataProvider.java index a4799cb..faecbe3 100644 --- a/main/src/cgeo/geocaching/sensors/GeoDataProvider.java +++ b/main/src/cgeo/geocaching/sensors/GeoDataProvider.java @@ -1,25 +1,13 @@ package cgeo.geocaching.sensors; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.StartableHandlerThread; +import cgeo.geocaching.utils.RxUtils.LooperCallbacks; import org.apache.commons.lang3.StringUtils; import rx.Observable; -import rx.Observable.OnSubscribe; -import rx.Subscriber; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action0; -import rx.functions.Action1; -import rx.observables.ConnectableObservable; -import rx.subjects.BehaviorSubject; -import rx.subscriptions.CompositeSubscription; -import rx.subscriptions.Subscriptions; import android.content.Context; -import android.location.GpsSatellite; -import android.location.GpsStatus; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; @@ -27,40 +15,13 @@ import android.os.Bundle; import java.util.concurrent.TimeUnit; -public class GeoDataProvider implements OnSubscribe<IGeoData> { +public class GeoDataProvider extends LooperCallbacks<IGeoData> { - private static final String LAST_LOCATION_PSEUDO_PROVIDER = "last"; + private final Context context; private final LocationManager geoManager; - private final LocationData gpsLocation = new LocationData(); - private final LocationData netLocation = new LocationData(); - private final BehaviorSubject<IGeoData> subject; - private static final StartableHandlerThread handlerThread = - new StartableHandlerThread("GeoDataProvider thread", android.os.Process.THREAD_PRIORITY_BACKGROUND); - static { - handlerThread.start(); - } - - 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 Location latestGPSLocation = null; + private final Listener networkListener = new Listener(); + private final Listener gpsListener = new Listener(); /** * Build a new geo data provider object. @@ -71,117 +32,51 @@ public class GeoDataProvider implements OnSubscribe<IGeoData> { * @param context the context used to retrieve the system services */ protected GeoDataProvider(final Context context) { - geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - subject = BehaviorSubject.create(findInitialLocation()); + super(2500, TimeUnit.MILLISECONDS); + this.context = context.getApplicationContext(); + geoManager = (LocationManager) this.context.getSystemService(Context.LOCATION_SERVICE); } public static Observable<IGeoData> create(final Context context) { - final GeoDataProvider provider = new GeoDataProvider(context); - return provider.worker.refCount(); + return Observable.create(new GeoDataProvider(context)); } @Override - public void call(final Subscriber<? super IGeoData> subscriber) { - subject.subscribe(subscriber); - } - - final ConnectableObservable<IGeoData> worker = new ConnectableObservable<IGeoData>(this) { - private int debugSessionCounter = 0; - - private final Object lock = new Object(); - private int count = 0; - - final private GpsStatus.Listener gpsStatusListener = new GpsStatusListener(); - final private Listener networkListener = new Listener(LocationManager.NETWORK_PROVIDER, netLocation); - final private Listener gpsListener = new Listener(LocationManager.GPS_PROVIDER, gpsLocation); - - @Override - public void connect(Action1<? super Subscription> connection) { - final CompositeSubscription subscription = new CompositeSubscription(); - AndroidSchedulers.handlerThread(handlerThread.getHandler()).createWorker().schedule(new Action0() { - @Override - public void call() { - synchronized(lock) { - if (count++ == 0) { - Log.d("GeoDataProvider: starting the GPS and network listeners" + " (" + ++debugSessionCounter + ")"); - 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); - } - } - } - } - - subscription.add(Subscriptions.create(new Action0() { - @Override - public void call() { - AndroidSchedulers.handlerThread(handlerThread.getHandler()).createWorker().schedule(new Action0() { - @Override - public void call() { - synchronized (lock) { - if (--count == 0) { - Log.d("GeoDataProvider: stopping the GPS and network listeners" + " (" + debugSessionCounter + ")"); - geoManager.removeUpdates(networkListener); - geoManager.removeUpdates(gpsListener); - geoManager.removeGpsStatusListener(gpsStatusListener); - } - } - } - }, 2500, TimeUnit.MILLISECONDS); - } - })); - } - }); - connection.call(subscription); + public void onStart() { + final IGeoData initialLocation = GeoData.getInitialLocation(context); + if (initialLocation != null) { + subscriber.onNext(initialLocation); } - }; - - private IGeoData findInitialLocation() { - final Location initialLocation = new Location(LAST_LOCATION_PSEUDO_PROVIDER); + Log.d("GeoDataProvider: starting the GPS and network listeners"); try { - // Try to find a sensible initial location from the last locations known to Android. - final Location lastGpsLocation = geoManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); - final Location lastNetworkLocation = geoManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); - - // If both providers are non-null, take the most recent one - if (lastGpsLocation != null && lastNetworkLocation != null) { - if (lastGpsLocation.getTime() >= lastNetworkLocation.getTime()) { - copyCoords(initialLocation, lastGpsLocation); - } else { - copyCoords(initialLocation, lastNetworkLocation); - } - } else if (lastGpsLocation != null) { - copyCoords(initialLocation, lastGpsLocation); - } else if (lastNetworkLocation != null) { - copyCoords(initialLocation, lastNetworkLocation); - } else { - Log.i("GeoDataProvider: no last known location available"); - } + geoManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, gpsListener); + } catch (final Exception e) { + Log.w("Unable to create GPS location provider: " + e.getMessage()); + } + try { + geoManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, networkListener); } catch (final Exception e) { - // This error is non-fatal as its only consequence is that we will start with a dummy location - // instead of a previously known one. - Log.e("GeoDataProvider: error when retrieving last known location", e); + Log.w("Unable to create network location provider: " + e.getMessage()); } - // Start with an historical GeoData just in case someone queries it before we get - // a chance to get any information. - return new GeoData(initialLocation, false, 0, 0, true); } - private static void copyCoords(final Location target, final Location source) { - target.setLatitude(source.getLatitude()); - target.setLongitude(source.getLongitude()); + @Override + protected void onStop() { + Log.d("GeoDataProvider: stopping the GPS and network listeners"); + geoManager.removeUpdates(networkListener); + geoManager.removeUpdates(gpsListener); } 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 onLocationChanged(final Location location) { + if (StringUtils.equals(location.getProvider(), LocationManager.GPS_PROVIDER)) { + latestGPSLocation = location; + assign(latestGPSLocation); + } else { + assign(GeoData.best(latestGPSLocation, location)); + } } @Override @@ -198,86 +93,12 @@ public class GeoDataProvider implements OnSubscribe<IGeoData> { 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_FIRST_FIX: - 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; - default: - throw new IllegalStateException(); - } - - 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; - } - + private void assign(final Location location) { // We do not necessarily get signalled when satellites go to 0/0. - final int visible = gpsLocation.isRecent() ? satellitesVisible : 0; - final boolean pseudoLocation = StringUtils.equals(locationData.location.getProvider(), LAST_LOCATION_PSEUDO_PROVIDER); - final IGeoData current = new GeoData(locationData.location, gpsEnabled, visible, satellitesFixed, pseudoLocation); - subject.onNext(current); + final IGeoData current = new GeoData(location); + subscriber.onNext(current); } } diff --git a/main/src/cgeo/geocaching/sensors/GeoDirHandler.java b/main/src/cgeo/geocaching/sensors/GeoDirHandler.java index 0f30142..d127784 100644 --- a/main/src/cgeo/geocaching/sensors/GeoDirHandler.java +++ b/main/src/cgeo/geocaching/sensors/GeoDirHandler.java @@ -2,6 +2,7 @@ package cgeo.geocaching.sensors; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.utils.AngleUtils; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -21,7 +22,7 @@ import rx.subscriptions.CompositeSubscription; * accordingly in {@code onPause}. * * The direction is always relative to the top of the device (natural direction), and that it must - * be fixed using {@link DirectionProvider#getDirectionNow(float)}. When the direction is derived from the GPS, + * be fixed using {@link cgeo.geocaching.utils.AngleUtils#getDirectionNow(float)}. When the direction is derived from the GPS, * it is altered so that the fix can still be applied as if the information came from the compass. */ public abstract class GeoDirHandler { @@ -29,6 +30,7 @@ public abstract class GeoDirHandler { public static final int UPDATE_GEODATA = 1 << 1; public static final int UPDATE_DIRECTION = 1 << 2; public static final int UPDATE_GEODIR = 1 << 3; + public static final int LOW_POWER = 1 << 4; private static final CgeoApplication app = CgeoApplication.getInstance(); @@ -76,7 +78,7 @@ public abstract class GeoDirHandler { private static float fixDirection(final IGeoData geoData, final float direction) { final boolean useGPSBearing = !Settings.isUseCompass() || geoData.getSpeed() > 5; - return useGPSBearing ? DirectionProvider.reverseDirectionNow(geoData.getBearing()) : direction; + return useGPSBearing ? AngleUtils.reverseDirectionNow(geoData.getBearing()) : direction; } /** @@ -85,8 +87,9 @@ public abstract class GeoDirHandler { */ public Subscription start(final int flags) { final CompositeSubscription subscriptions = new CompositeSubscription(); + final boolean lowPower = (flags & LOW_POWER) != 0; if ((flags & UPDATE_GEODATA) != 0) { - subscriptions.add(app.geoDataObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<IGeoData>() { + subscriptions.add(app.geoDataObservable(lowPower).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<IGeoData>() { @Override public void call(final IGeoData geoData) { updateGeoData(geoData); @@ -102,7 +105,7 @@ public abstract class GeoDirHandler { })); } if ((flags & UPDATE_GEODIR) != 0) { - subscriptions.add(Observable.combineLatest(app.geoDataObservable(), app.directionObservable(), new Func2<IGeoData, Float, ImmutablePair<IGeoData, Float>>() { + subscriptions.add(Observable.combineLatest(app.geoDataObservable(lowPower), app.directionObservable(), new Func2<IGeoData, Float, ImmutablePair<IGeoData, Float>>() { @Override public ImmutablePair<IGeoData, Float> call(final IGeoData geoData, final Float direction) { return ImmutablePair.of(geoData, fixDirection(geoData, direction)); diff --git a/main/src/cgeo/geocaching/sensors/GpsStatusProvider.java b/main/src/cgeo/geocaching/sensors/GpsStatusProvider.java new file mode 100644 index 0000000..5f12e99 --- /dev/null +++ b/main/src/cgeo/geocaching/sensors/GpsStatusProvider.java @@ -0,0 +1,99 @@ +package cgeo.geocaching.sensors; + +import cgeo.geocaching.sensors.GpsStatusProvider.Status; +import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils.LooperCallbacks; + +import rx.Observable; + +import android.content.Context; +import android.location.GpsSatellite; +import android.location.GpsStatus; +import android.location.LocationManager; + +public class GpsStatusProvider extends LooperCallbacks<Status> { + + public static class Status { + final public boolean gpsEnabled; + final public int satellitesVisible; + final public int satellitesFixed; + + public Status(final boolean gpsEnabled, final int satellitesVisible, final int satellitesFixed) { + this.gpsEnabled = gpsEnabled; + this.satellitesVisible = satellitesVisible; + this.satellitesFixed = satellitesFixed; + } + } + + private final LocationManager geoManager; + private final GpsStatus.Listener gpsStatusListener = new GpsStatusListener(); + private Status latest = new Status(false, 0, 0); + + private static final Status NO_GPS = new Status(false, 0, 0); + + /** + * Build a new gps status 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 + */ + protected GpsStatusProvider(final Context context) { + geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + } + + public static Observable<Status> create(final Context context) { + return Observable.create(new GpsStatusProvider(context)); + } + + @Override + protected void onStart() { + Log.d("GpsStatusProvider: starting the GPS status listener"); + subscriber.onNext(NO_GPS); + geoManager.addGpsStatusListener(gpsStatusListener); + } + + @Override + protected void onStop() { + Log.d("GpsStatusProvider: stopping the GPS status listener"); + geoManager.removeGpsStatusListener(gpsStatusListener); + } + + private final class GpsStatusListener implements GpsStatus.Listener { + + @Override + public void onGpsStatusChanged(final int event) { + switch (event) { + case GpsStatus.GPS_EVENT_FIRST_FIX: + 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 == latest.satellitesVisible && fixed == latest.satellitesFixed) { + return; + } + latest = new Status(true, visible, fixed); + break; + } + case GpsStatus.GPS_EVENT_STARTED: + latest = new Status(true, 0, 0); + break; + case GpsStatus.GPS_EVENT_STOPPED: + latest = new Status(false, 0, 0); + break; + default: + throw new IllegalStateException(); + } + + subscriber.onNext(latest); + } + } + +} diff --git a/main/src/cgeo/geocaching/sensors/IGeoData.java b/main/src/cgeo/geocaching/sensors/IGeoData.java index 5b4f046..b78b805 100644 --- a/main/src/cgeo/geocaching/sensors/IGeoData.java +++ b/main/src/cgeo/geocaching/sensors/IGeoData.java @@ -10,13 +10,8 @@ public interface IGeoData { public Location getLocation(); public LocationProviderType getLocationProvider(); - public boolean isPseudoLocation(); - public Geopoint getCoords(); public float getBearing(); public float getSpeed(); public float getAccuracy(); - public boolean getGpsEnabled(); - public int getSatellitesVisible(); - public int getSatellitesFixed(); } diff --git a/main/src/cgeo/geocaching/sensors/OrientationProvider.java b/main/src/cgeo/geocaching/sensors/OrientationProvider.java new file mode 100644 index 0000000..83e0638 --- /dev/null +++ b/main/src/cgeo/geocaching/sensors/OrientationProvider.java @@ -0,0 +1,67 @@ +package cgeo.geocaching.sensors; + +import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils.LooperCallbacks; + +import rx.Observable; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +public class OrientationProvider extends LooperCallbacks<Float> implements SensorEventListener { + + private final SensorManager sensorManager; + private final Sensor orientationSensor; + + @SuppressWarnings("deprecation") + protected OrientationProvider(final Context context) { + sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + orientationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); + if (orientationSensor != null) { + Log.d("OrientationProvider: sensor found"); + } else { + Log.w("OrientationProvider: no orientation sensor on this device"); + } + } + + @Override + public void onSensorChanged(final SensorEvent event) { + subscriber.onNext(event.values[0]); + } + + @Override + public void onAccuracyChanged(final Sensor sensor, final int accuracy) { + /* + * There is a bug in Android, which apparently causes this method to be called every + * time the sensor _value_ changed, even if the _accuracy_ did not change. Do not have any code in here. + * + * See for example https://code.google.com/p/android/issues/detail?id=14792 + */ + } + + @Override + public void onStart() { + if (orientationSensor != null) { + Log.d("OrientationProvider: starting the orientation provider"); + sensorManager.registerListener(this, orientationSensor, SensorManager.SENSOR_DELAY_NORMAL); + } else { + subscriber.onError(new RuntimeException("orientation sensor is absent on this device")); + } + } + + @Override + public void onStop() { + if (orientationSensor != null) { + Log.d("OrientationProvider: stopping the orientation provider"); + sensorManager.unregisterListener(this); + } + } + + public static Observable<Float> create(final Context context) { + return Observable.create(new OrientationProvider(context)); + } + +} diff --git a/main/src/cgeo/geocaching/sensors/RotationProvider.java b/main/src/cgeo/geocaching/sensors/RotationProvider.java new file mode 100644 index 0000000..40e2c3c --- /dev/null +++ b/main/src/cgeo/geocaching/sensors/RotationProvider.java @@ -0,0 +1,83 @@ +package cgeo.geocaching.sensors; + +import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils.LooperCallbacks; + +import rx.Observable; + +import android.annotation.TargetApi; +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +public class RotationProvider extends LooperCallbacks<Float> implements SensorEventListener { + + private final SensorManager sensorManager; + private final Sensor rotationSensor; + private final float[] rotationMatrix = new float[16]; + private final float[] orientation = new float[4]; + private final float[] values = new float[4]; + + @TargetApi(19) + protected RotationProvider(final Context context, final boolean lowPower) { + sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + // The geomagnetic rotation vector introduced in Android 4.4 (API 19) requires less power. Favour it + // even if it is more sensible to noise in low-power settings. + final Sensor sensor = lowPower ? sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR) : null; + if (sensor != null) { + rotationSensor = sensor; + Log.d("RotationProvider: geomagnetic (low-power) sensor found"); + } else { + rotationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); + if (rotationSensor != null) { + Log.d("RotationProvider: sensor found"); + } else { + Log.w("RotationProvider: no rotation sensor on this device"); + } + } + } + + @Override + public void onSensorChanged(final SensorEvent event) { + // On some Samsung devices, SensorManager#getRotationMatrixFromVector throws an exception if the rotation + // vector has more than 4 elements. Since only the four first elements are used, we can truncate the vector + // without losing precision. + if (event.values.length > 4) { + System.arraycopy(event.values, 0, values, 0, 4); + SensorManager.getRotationMatrixFromVector(rotationMatrix, values); + } else { + SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values); + } + SensorManager.getOrientation(rotationMatrix, orientation); + subscriber.onNext((float) (orientation[0] * 180 / Math.PI)); + } + + @Override + public void onAccuracyChanged(final Sensor sensor, final int accuracy) { + } + + @Override + public void onStart() { + if (rotationSensor != null) { + Log.d("RotationProvider: starting the rotation provider"); + sensorManager.registerListener(this, rotationSensor, SensorManager.SENSOR_DELAY_NORMAL); + } else { + subscriber.onError(new RuntimeException("rotation sensor is absent on this device")); + } + } + + @Override + public void onStop() { + if (rotationSensor != null) { + Log.d("RotationProvider: stopping the rotation provider"); + sensorManager.unregisterListener(this); + } + } + + public static Observable<Float> create(final Context context, final boolean lowPower) { + return Observable.create(new RotationProvider(context, lowPower)); + } + +} |
