aboutsummaryrefslogtreecommitdiffstats
path: root/main/src/cgeo/geocaching/playservices/LocationProvider.java
diff options
context:
space:
mode:
Diffstat (limited to 'main/src/cgeo/geocaching/playservices/LocationProvider.java')
-rw-r--r--main/src/cgeo/geocaching/playservices/LocationProvider.java160
1 files changed, 160 insertions, 0 deletions
diff --git a/main/src/cgeo/geocaching/playservices/LocationProvider.java b/main/src/cgeo/geocaching/playservices/LocationProvider.java
new file mode 100644
index 0000000..027ae29
--- /dev/null
+++ b/main/src/cgeo/geocaching/playservices/LocationProvider.java
@@ -0,0 +1,160 @@
+package cgeo.geocaching.playservices;
+
+import cgeo.geocaching.sensors.GeoData;
+import cgeo.geocaching.settings.Settings;
+import cgeo.geocaching.utils.Log;
+import cgeo.geocaching.utils.RxUtils;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.location.LocationListener;
+import com.google.android.gms.location.LocationRequest;
+import com.google.android.gms.location.LocationServices;
+
+import rx.Observable;
+import rx.Observable.OnSubscribe;
+import rx.Subscriber;
+import rx.functions.Action0;
+import rx.functions.Func1;
+import rx.observers.Subscribers;
+import rx.subjects.ReplaySubject;
+import rx.subscriptions.Subscriptions;
+
+import android.content.Context;
+import android.location.Location;
+import android.os.Bundle;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class LocationProvider implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {
+
+ private static final LocationRequest LOCATION_REQUEST =
+ LocationRequest.create().setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY).setInterval(2000).setFastestInterval(250);
+ private static final LocationRequest LOCATION_REQUEST_LOW_POWER =
+ LocationRequest.create().setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY).setInterval(10000).setFastestInterval(5000);
+ private static final AtomicInteger mostPreciseCount = new AtomicInteger(0);
+ private static final AtomicInteger lowPowerCount = new AtomicInteger(0);
+ private static LocationProvider instance = null;
+ private static final ReplaySubject<GeoData> subject = ReplaySubject.createWithSize(1);
+ private final GoogleApiClient locationClient;
+
+ private static synchronized LocationProvider getInstance(final Context context) {
+ if (instance == null) {
+ instance = new LocationProvider(context);
+ }
+ return instance;
+ }
+
+ private synchronized void updateRequest() {
+ if (locationClient.isConnected()) {
+ if (mostPreciseCount.get() > 0) {
+ Log.d("LocationProvider: requesting most precise locations");
+ LocationServices.FusedLocationApi.requestLocationUpdates(locationClient, LOCATION_REQUEST, this, RxUtils.looperCallbacksLooper);
+ } else if (lowPowerCount.get() > 0) {
+ Log.d("LocationProvider: requesting low-power locations");
+ LocationServices.FusedLocationApi.requestLocationUpdates(locationClient, LOCATION_REQUEST_LOW_POWER, this, RxUtils.looperCallbacksLooper);
+ } else {
+ Log.d("LocationProvider: stopping location requests");
+ LocationServices.FusedLocationApi.removeLocationUpdates(locationClient, this);
+ }
+ }
+ }
+
+ private static Observable<GeoData> get(final Context context, final AtomicInteger reference) {
+ final LocationProvider instance = getInstance(context);
+ return Observable.create(new OnSubscribe<GeoData>() {
+ @Override
+ public void call(final Subscriber<? super GeoData> subscriber) {
+ if (reference.incrementAndGet() == 1) {
+ instance.updateRequest();
+ }
+ subscriber.add(Subscriptions.create(new Action0() {
+ @Override
+ public void call() {
+ RxUtils.looperCallbacksWorker.schedule(new Action0() {
+ @Override
+ public void call() {
+ if (reference.decrementAndGet() == 0) {
+ instance.updateRequest();
+ }
+ }
+ }, 2500, TimeUnit.MILLISECONDS);
+ }
+ }));
+ subscriber.add(subject.subscribe(Subscribers.from(subscriber)));
+ }
+ });
+ }
+
+ public static Observable<GeoData> getMostPrecise(final Context context) {
+ return get(context, mostPreciseCount).onBackpressureDrop();
+ }
+
+ public static Observable<GeoData> getLowPower(final Context context) {
+ // Low-power location without the last stored location
+ final Observable<GeoData> lowPowerObservable = get(context, lowPowerCount).skip(1);
+
+ // High-power location without the last stored location
+ final Observable<GeoData> highPowerObservable = get(context, mostPreciseCount).skip(1);
+
+ // Use either low-power (with a 6 seconds head start) or high-power observables to obtain a location
+ // no less precise than 20 meters.
+ final Observable<GeoData> untilPreciseEnoughObservable =
+ lowPowerObservable.mergeWith(highPowerObservable.delaySubscription(6, TimeUnit.SECONDS))
+ .takeUntil(new Func1<GeoData, Boolean>() {
+ @Override
+ public Boolean call(final GeoData geoData) {
+ return geoData.getAccuracy() <= 20;
+ }
+ });
+
+ // After sending the last known location, try to get a precise location then use the low-power mode. If no
+ // location information is given for 25 seconds (if the network location is turned off for example), get
+ // back to the precise location and try again.
+ return subject.first().concatWith(untilPreciseEnoughObservable.concatWith(lowPowerObservable).timeout(25, TimeUnit.SECONDS).retry()).onBackpressureDrop();
+ }
+
+ /**
+ * 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
+ */
+ private LocationProvider(final Context context) {
+ final GeoData initialLocation = GeoData.getInitialLocation(context);
+ subject.onNext(initialLocation != null ? initialLocation : GeoData.DUMMY_LOCATION);
+ locationClient = new GoogleApiClient.Builder(context)
+ .addApi(LocationServices.API)
+ .addConnectionCallbacks(this)
+ .addOnConnectionFailedListener(this)
+ .build();
+ locationClient.connect();
+ }
+
+ @Override
+ public void onConnected(final Bundle bundle) {
+ updateRequest();
+ }
+
+ @Override
+ public void onConnectionFailed(final ConnectionResult connectionResult) {
+ Log.e("cannot connect to Google Play location service: " + connectionResult);
+ subject.onError(new RuntimeException("Connection failed: " + connectionResult));
+ }
+
+ @Override
+ public void onLocationChanged(final Location location) {
+ if (Settings.useLowPowerMode()) {
+ location.setProvider(GeoData.LOW_POWER_PROVIDER);
+ }
+ subject.onNext(new GeoData(location));
+ }
+
+ @Override
+ public void onConnectionSuspended(final int arg0) {
+ // empty
+ }
+}