package com.gaiagps.iburn.location; import android.content.Context; import android.location.Location; import android.os.Build; import android.os.SystemClock; import com.gaiagps.iburn.BuildConfig; import com.gaiagps.iburn.PermissionManager; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.maps.LocationSource; import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import pl.charmas.android.reactivelocation.ReactiveLocationProvider; import rx.Observable; import rx.Subscription; import rx.subjects.PublishSubject; import timber.log.Timber; /** * Fulfills requests for location and supports mocking based on the value of {@link BuildConfig#MOCK} * Created by davidbrodsky on 7/5/15. */ public class LocationProvider { private static ReactiveLocationProvider locationProvider; // Location Mocking private static AtomicBoolean isMockingLocation = new AtomicBoolean(false); private static Subscription mockLocationSubscription; private static Location lastMockLocation; private static PublishSubject<Location> mockLocationSubject = PublishSubject.create(); private static final double MAX_MOCK_LAT = 40.8037; private static final double MIN_MOCK_LAT = 40.7727; private static final double MAX_MOCK_LON = -119.1851; private static final double MIN_MOCK_LON = -119.2210; public static Observable<Location> getLastLocation(Context context) { init(context); if (BuildConfig.MOCK) { return Observable.just(lastMockLocation); } else { if (!PermissionManager.hasLocationPermissions(context)) { // TODO: stall location result until permission ready return Observable.empty(); } return locationProvider.getLastKnownLocation(); } } public static Observable<Location> observeCurrentLocation(Context context, LocationRequest request) { init(context); if (BuildConfig.MOCK) { return mockLocationSubject.startWith(lastMockLocation); } else { if (!PermissionManager.hasLocationPermissions(context)) { // TODO: stall location result until permission ready return Observable.empty(); } return locationProvider.getUpdatedLocation(request); } } private static void init(Context context) { if (locationProvider == null) { locationProvider = new ReactiveLocationProvider(context); if (BuildConfig.MOCK) mockCurrentLocation(); } } /** * @return a mock {@link Location} generally within the bounds of BRC */ public static Location createMockLocation() { Location mockLocation = new Location("mock"); double mockLat = (Math.random() * (MAX_MOCK_LAT - MIN_MOCK_LAT)) + MIN_MOCK_LAT; double mockLon = (Math.random() * (MAX_MOCK_LON - MIN_MOCK_LON)) + MIN_MOCK_LON; mockLocation.setLatitude(mockLat); mockLocation.setLongitude(mockLon); mockLocation.setAccuracy(1.0f); mockLocation.setBearing(.4f); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { mockLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); } mockLocation.setTime(new Date().getTime()); // TODO : Should we use mocked date here as well? return mockLocation; } private static void mockCurrentLocation() { if (!isMockingLocation.get()) { lastMockLocation = createMockLocation(); isMockingLocation.set(true); mockLocationSubscription = Observable.interval(60, 60, TimeUnit.SECONDS) .startWith(-1l) .subscribe(time -> { lastMockLocation = createMockLocation(); mockLocationSubject.onNext(lastMockLocation); }); } } /** * A Mock {@link LocationSource} for use with a GoogleMap */ public static class MockLocationSource implements LocationSource { private Subscription locationSubscription; @Override public void activate(final OnLocationChangedListener onLocationChangedListener) { mockCurrentLocation(); locationSubscription = mockLocationSubject .startWith(lastMockLocation) .subscribe(onLocationChangedListener::onLocationChanged, throwable -> Timber.e(throwable, "Error sending mock location to map")); } @Override public void deactivate() { if (locationSubscription != null) { locationSubscription.unsubscribe(); locationSubscription = null; } } } }