<NME> LocationProvider.java
<BEF> package cgeo.geocaching.playservices;

import cgeo.geocaching.sensors.GeoData;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.AndroidRxUtils;
import cgeo.geocaching.utils.Log;

import android.content.Context;
import android.location.Location;

import androidx.annotation.NonNull;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import rx.Observable.OnSubscribe;
import rx.Subscriber;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.subjects.ReplaySubject;
import rx.subscriptions.Subscriptions;

import io.reactivex.rxjava3.subjects.ReplaySubject;

public class LocationProvider extends LocationCallback {

    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 FusedLocationProviderClient fusedLocationClient;
    private static final AtomicInteger mostPreciseCount = new AtomicInteger(0);
    private static final AtomicInteger lowPowerCount = new AtomicInteger(0);
    private static LocationProvider instance = null;
    private static ReplaySubject<GeoData> subject = ReplaySubject.createWithSize(1);
    private final LocationClient locationClient;

    private static synchronized LocationProvider getInstance(final Context context) {

    private synchronized void updateRequest() {
        try {
            if (mostPreciseCount.get() > 0) {
                Log.d("LocationProvider: requesting most precise locations");
                fusedLocationClient.requestLocationUpdates(
                        LOCATION_REQUEST,
                        this,
                        AndroidRxUtils.looperCallbacksLooper
                );
            } else if (lowPowerCount.get() > 0) {
                Log.d("LocationProvider: requesting low-power locations");
                fusedLocationClient.requestLocationUpdates(
                        LOCATION_REQUEST_LOW_POWER,
                        this,
                        AndroidRxUtils.looperCallbacksLooper
                );
            } else {
                Log.d("LocationProvider: stopping location requests");
                fusedLocationClient.removeLocationUpdates(this);
            }
        } catch (final SecurityException e) {
            Log.w("Security exception when accessing fused location services", e);
        }
    }

    private static Observable<GeoData> get(final Context context, final AtomicInteger reference) {
        final LocationProvider instance = getInstance(context);

        return Observable.create(emitter -> {
            if (reference.incrementAndGet() == 1) {
                instance.updateRequest();
            }
            final Disposable disposable = subject.subscribeWith(new DisposableObserver<GeoData>() {
                @Override
                public void onNext(@NonNull final GeoData value) {
                    emitter.onNext(value);
                }

                        }, 2500, TimeUnit.MILLISECONDS);
                    }
                }));
                subscriber.add(subject.subscribe(new Action1<GeoData>() {
                    @Override
                    public void call(final GeoData geoData) {
                        subscriber.onNext(geoData);
                    }
                }));
            }
        });
    }
                    emitter.onComplete();
                }
            });
            emitter.setDisposable(Disposable.fromRunnable(() -> {
                disposable.dispose();
                AndroidRxUtils.looperCallbacksScheduler.scheduleDirect(() -> {
                    if (reference.decrementAndGet() == 0) {
                        instance.updateRequest();
                    }
                }, 2500, TimeUnit.MILLISECONDS);
            }));
        });
    }

    public static Observable<GeoData> getMostPrecise(final Context context) {
        return get(context, mostPreciseCount);
    }

    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(geoData -> 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.take(1).concatWith(untilPreciseEnoughObservable.concatWith(lowPowerObservable).timeout(25, TimeUnit.SECONDS).retry());
    }

    /**
     * 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);

        fusedLocationClient = LocationServices.getFusedLocationProviderClient(context);
    }

    @Override
    public void onLocationResult(final LocationResult result) {
        final Location location = result.getLastLocation();
        if (Settings.useLowPowerMode()) {
            location.setProvider(GeoData.LOW_POWER_PROVIDER);
        }
        subject.onNext(new GeoData(location));
    }
}

<MSG> Merge branch 'release' into upstream

<DFF> @@ -16,8 +16,8 @@ import rx.Observable;
 import rx.Observable.OnSubscribe;
 import rx.Subscriber;
 import rx.functions.Action0;
-import rx.functions.Action1;
 import rx.functions.Func1;
+import rx.observers.Subscribers;
 import rx.subjects.ReplaySubject;
 import rx.subscriptions.Subscriptions;
 
@@ -37,7 +37,7 @@ public class LocationProvider implements ConnectionCallbacks, OnConnectionFailed
     private static final AtomicInteger mostPreciseCount = new AtomicInteger(0);
     private static final AtomicInteger lowPowerCount = new AtomicInteger(0);
     private static LocationProvider instance = null;
-    private static ReplaySubject<GeoData> subject = ReplaySubject.createWithSize(1);
+    private static final ReplaySubject<GeoData> subject = ReplaySubject.createWithSize(1);
     private final LocationClient locationClient;
 
     private static synchronized LocationProvider getInstance(final Context context) {
@@ -83,12 +83,7 @@ public class LocationProvider implements ConnectionCallbacks, OnConnectionFailed
                         }, 2500, TimeUnit.MILLISECONDS);
                     }
                 }));
-                subscriber.add(subject.subscribe(new Action1<GeoData>() {
-                    @Override
-                    public void call(final GeoData geoData) {
-                        subscriber.onNext(geoData);
-                    }
-                }));
+                subscriber.add(subject.subscribe(Subscribers.from(subscriber)));
             }
         });
     }

Merge branch 'release' into upstream