aboutsummaryrefslogtreecommitdiffstats
path: root/main/src/cgeo/geocaching/sensors
diff options
context:
space:
mode:
Diffstat (limited to 'main/src/cgeo/geocaching/sensors')
-rw-r--r--main/src/cgeo/geocaching/sensors/DirectionProvider.java146
-rw-r--r--main/src/cgeo/geocaching/sensors/GeoData.java87
-rw-r--r--main/src/cgeo/geocaching/sensors/GeoDataProvider.java255
-rw-r--r--main/src/cgeo/geocaching/sensors/GeoDirHandler.java11
-rw-r--r--main/src/cgeo/geocaching/sensors/GpsStatusProvider.java99
-rw-r--r--main/src/cgeo/geocaching/sensors/IGeoData.java5
-rw-r--r--main/src/cgeo/geocaching/sensors/OrientationProvider.java67
-rw-r--r--main/src/cgeo/geocaching/sensors/RotationProvider.java83
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));
+ }
+
+}