diff --git a/.gitignore b/.gitignore index 8a4e3d4c..e460a901 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ build/ .DS_Store *.iml +/sample/keys/ +/sample/properties/ diff --git a/android-reactive-location/build.gradle b/android-reactive-location/build.gradle index fc254d07..9ff949df 100644 --- a/android-reactive-location/build.gradle +++ b/android-reactive-location/build.gradle @@ -1,4 +1,6 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' apply plugin: 'maven' android { @@ -6,22 +8,28 @@ android { buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - minSdkVersion 14 + minSdkVersion 16 targetSdkVersion rootProject.ext.targetSdkVersion } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } } //TODO: local maven deployment dependencies { - compile 'com.google.android.gms:play-services-location:11.0.4' - compile 'com.google.android.gms:play-services-places:11.0.4' - compile 'io.reactivex.rxjava2:rxjava:2.0.5' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation "androidx.core:core-ktx:1.3.2" + + implementation ('com.google.android.gms:play-services-location:17.1.0'){ + exclude group: 'com.google.android.gms', module: 'play-services-places' + } + implementation 'com.google.android.libraries.places:places:2.4.0' + implementation 'io.reactivex.rxjava2:rxjava:2.2.20' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' } // Comment this to deploy to local maven repository diff --git a/android-reactive-location/src/main/AndroidManifest.xml b/android-reactive-location/src/main/AndroidManifest.xml index cd701d43..311c1a8a 100644 --- a/android-reactive-location/src/main/AndroidManifest.xml +++ b/android-reactive-location/src/main/AndroidManifest.xml @@ -1,5 +1,2 @@ - - - - + diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.java deleted file mode 100644 index ad1e57fb..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.java +++ /dev/null @@ -1,45 +0,0 @@ -package pl.charmas.android.reactivelocation2; - -import com.google.android.gms.common.data.AbstractDataBuffer; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import io.reactivex.ObservableOnSubscribe; -import io.reactivex.disposables.Disposables; -import io.reactivex.functions.Action; - - -/** - * Util class that creates observable from buffer. - */ -public final class DataBufferObservable { - - private DataBufferObservable() { - //no instance - } - - /** - * Creates observable from buffer. On unsubscribe buffer is automatically released. - * - * @param buffer source buffer - * @param item type - * @return observable that emits all items from buffer and on unsubscription releases it - */ - public static Observable from(final AbstractDataBuffer buffer) { - return Observable.create(new ObservableOnSubscribe() { - - @Override - public void subscribe(final ObservableEmitter emitter) { - for (T item : buffer) { - emitter.onNext(item); - } - emitter.setDisposable(Disposables.fromAction(new Action() { - @Override - public void run() throws Exception { - buffer.release(); - } - })); - } - }); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt new file mode 100644 index 00000000..12735a21 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.kt @@ -0,0 +1,34 @@ +package pl.charmas.android.reactivelocation2 + +import com.google.android.gms.common.data.DataBuffer +import io.reactivex.Observable +import io.reactivex.ObservableEmitter +import io.reactivex.disposables.Disposables + +/** + * Util class that creates observable from buffer. + */ +object DataBufferObservable { + /** + * Creates observable from buffer. On unsubscribe buffer is automatically released. + * + * @param buffer source buffer + * @param item type + * @return observable that emits all items from buffer and on unsubscription releases it + */ + fun from(buffer: DataBuffer): Observable { + return Observable.create { emitter -> + emitter.setDisposable(Disposables.fromAction { buffer.release() }) + for (item in buffer) { + if (!emitter.isDisposed) { + if (item != null) { + emitter.onNext(item) + } + } + } + if (!emitter.isDisposed) { + emitter.onComplete() + } + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java deleted file mode 100644 index f6c3533b..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java +++ /dev/null @@ -1,455 +0,0 @@ -package pl.charmas.android.reactivelocation2; - -import android.app.PendingIntent; -import android.content.Context; -import android.location.Address; -import android.location.Location; -import android.os.Handler; -import android.support.annotation.Nullable; -import android.support.annotation.RequiresPermission; - -import com.google.android.gms.common.api.Api; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.PendingResult; -import com.google.android.gms.common.api.Result; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.ActivityRecognitionResult; -import com.google.android.gms.location.GeofencingRequest; -import com.google.android.gms.location.LocationRequest; -import com.google.android.gms.location.LocationServices; -import com.google.android.gms.location.LocationSettingsRequest; -import com.google.android.gms.location.LocationSettingsResult; -import com.google.android.gms.location.places.AutocompleteFilter; -import com.google.android.gms.location.places.AutocompletePredictionBuffer; -import com.google.android.gms.location.places.PlaceBuffer; -import com.google.android.gms.location.places.PlaceFilter; -import com.google.android.gms.location.places.PlaceLikelihoodBuffer; -import com.google.android.gms.location.places.PlacePhotoMetadata; -import com.google.android.gms.location.places.PlacePhotoMetadataResult; -import com.google.android.gms.location.places.PlacePhotoResult; -import com.google.android.gms.location.places.Places; -import com.google.android.gms.maps.model.LatLngBounds; - -import java.util.List; -import java.util.Locale; - -import io.reactivex.Observable; -import io.reactivex.functions.Function; -import pl.charmas.android.reactivelocation2.observables.GoogleAPIClientObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; -import pl.charmas.android.reactivelocation2.observables.PendingResultObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.activity.ActivityUpdatesObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.geocode.GeocodeObservable; -import pl.charmas.android.reactivelocation2.observables.geocode.ReverseGeocodeObservable; -import pl.charmas.android.reactivelocation2.observables.geofence.AddGeofenceObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.geofence.RemoveGeofenceObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.location.AddLocationIntentUpdatesObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.location.LastKnownLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.location.LocationUpdatesObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.location.MockLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.location.RemoveLocationIntentUpdatesObservableOnSubscribe; - - -/** - * Factory of observables that can manipulate location - * delivered by Google Play Services. - */ -public class ReactiveLocationProvider { - private final ObservableContext ctx; - private final ObservableFactory factory; - - /** - * Creates location provider instance with default configuration. - * - * @param ctx preferably application context - */ - public ReactiveLocationProvider(Context ctx) { - this(ctx, ReactiveLocationProviderConfiguration.builder().build()); - } - - /** - * Create location provider with given {@link ReactiveLocationProviderConfiguration}. - * - * @param ctx preferably application context - * @param configuration configuration instance - */ - public ReactiveLocationProvider(Context ctx, ReactiveLocationProviderConfiguration configuration) { - this.ctx = new ObservableContext(ctx, configuration); - this.factory = new ObservableFactory(this.ctx); - } - - /** - * Creates location provider with custom handler in which all GooglePlayServices callbacks are called. - * - * @param ctx preferably application context - * @param handler on which all GooglePlayServices callbacks are called - * @see com.google.android.gms.common.api.GoogleApiClient.Builder#setHandler(android.os.Handler) - * @deprecated please use {@link ReactiveLocationProvider#ReactiveLocationProvider(Context, ReactiveLocationProviderConfiguration)} - */ - @Deprecated - public ReactiveLocationProvider(Context ctx, Handler handler) { - this(ctx, ReactiveLocationProviderConfiguration.builder().setCustomCallbackHandler(handler).build()); - } - - /** - * Creates observable that obtains last known location and than completes. - * Delivered location is never null - when it is unavailable Observable completes without emitting - * any value. - *

- * Observable can report {@link pl.charmas.android.reactivelocation2.observables.GoogleAPIConnectionException} - * when there are trouble connecting with Google Play Services and other exceptions that - * can be thrown on {@link com.google.android.gms.location.FusedLocationProviderApi#getLastLocation(com.google.android.gms.common.api.GoogleApiClient)}. - * Everything is delivered by {@link io.reactivex.Observer#onError(Throwable)}. - * - * @return observable that serves last know location - */ - @RequiresPermission( - anyOf = {"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"} - ) - public Observable getLastKnownLocation() { - return LastKnownLocationObservableOnSubscribe.createObservable(ctx, factory); - } - - /** - * Creates observable that allows to observe infinite stream of location updates. - * To stop the stream you have to unsubscribe from observable - location updates are - * then disconnected. - *

- * Observable can report {@link pl.charmas.android.reactivelocation2.observables.GoogleAPIConnectionException} - * when there are trouble connecting with Google Play Services and other exceptions that - * can be thrown on {@link com.google.android.gms.location.FusedLocationProviderApi#requestLocationUpdates(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.LocationRequest, com.google.android.gms.location.LocationListener)}. - * Everything is delivered by {@link io.reactivex.Observer#onError(Throwable)}. - * - * @param locationRequest request object with info about what kind of location you need - * @return observable that serves infinite stream of location updates - */ - @RequiresPermission( - anyOf = {"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"} - ) - public Observable getUpdatedLocation(LocationRequest locationRequest) { - return LocationUpdatesObservableOnSubscribe.createObservable(ctx, factory, locationRequest); - } - - /** - * Returns an observable which activates mock location mode when subscribed to, using the - * supplied observable as a source of mock locations. Mock locations will replace normal - * location information for all users of the FusedLocationProvider API on the device while this - * observable is subscribed to. - *

- * To use this method, mock locations must be enabled in developer options and your application - * must hold the android.permission.ACCESS_MOCK_LOCATION permission, or a {@link java.lang.SecurityException} - * will be thrown. - *

- * All statuses that are not successful will be reported as {@link pl.charmas.android.reactivelocation2.observables.StatusException}. - *

- * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. - * - * @param sourceLocationObservable observable that emits {@link android.location.Location} instances suitable to use as mock locations - * @return observable that emits {@link com.google.android.gms.common.api.Status} - */ - @RequiresPermission( - allOf = {"android.permission.ACCESS_COARSE_LOCATION", - "android.permission.ACCESS_MOCK_LOCATION"} - ) - public Observable mockLocation(Observable sourceLocationObservable) { - return MockLocationObservableOnSubscribe.createObservable(ctx, factory, sourceLocationObservable); - } - - /** - * Creates an observable that adds a {@link android.app.PendingIntent} as a location listener. - *

- * This invokes {@link com.google.android.gms.location.FusedLocationProviderApi#requestLocationUpdates(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.LocationRequest, android.app.PendingIntent)}. - *

- * When location updates are no longer required, a call to {@link #removeLocationUpdates(android.app.PendingIntent)} - * should be made. - *

- * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. - * - * @param locationRequest request object with info about what kind of location you need - * @param intent PendingIntent that will be called with location updates - * @return observable that adds the request and PendingIntent - */ - @RequiresPermission( - anyOf = {"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"} - ) - public Observable requestLocationUpdates(LocationRequest locationRequest, PendingIntent intent) { - return AddLocationIntentUpdatesObservableOnSubscribe.createObservable(ctx, factory, locationRequest, intent); - } - - /** - * Observable that can be used to remove {@link android.app.PendingIntent} location updates. - *

- * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. - * - * @param intent PendingIntent to remove location updates for - * @return observable that removes the PendingIntent - */ - public Observable removeLocationUpdates(PendingIntent intent) { - return RemoveLocationIntentUpdatesObservableOnSubscribe.createObservable(ctx, factory, intent); - } - - /** - * Creates observable that translates latitude and longitude to list of possible addresses using - * included Geocoder class. In case geocoder fails with IOException("Service not Available") fallback - * decoder is used using google web api. You should subscribe for this observable on I/O thread. - * The stream finishes after address list is available. - * - * @param lat latitude - * @param lng longitude - * @param maxResults maximal number of results you are interested in - * @return observable that serves list of address based on location - */ - public Observable> getReverseGeocodeObservable(double lat, double lng, int maxResults) { - return ReverseGeocodeObservable.createObservable(ctx.getContext(), factory, Locale.getDefault(), lat, lng, maxResults); - } - - /** - * Creates observable that translates latitude and longitude to list of possible addresses using - * included Geocoder class. In case geocoder fails with IOException("Service not Available") fallback - * decoder is used using google web api. You should subscribe for this observable on I/O thread. - * The stream finishes after address list is available. - * - * @param locale locale for address language - * @param lat latitude - * @param lng longitude - * @param maxResults maximal number of results you are interested in - * @return observable that serves list of address based on location - */ - public Observable> getReverseGeocodeObservable(Locale locale, double lat, double lng, int maxResults) { - return ReverseGeocodeObservable.createObservable(ctx.getContext(), factory, locale, lat, lng, maxResults); - } - - /** - * Creates observable that translates a street address or other description into a list of - * possible addresses using included Geocoder class. You should subscribe for this - * observable on I/O thread. - * The stream finishes after address list is available. - * - * @param locationName a user-supplied description of a location - * @param maxResults max number of results you are interested in - * @return observable that serves list of address based on location name - */ - public Observable> getGeocodeObservable(String locationName, int maxResults) { - return getGeocodeObservable(locationName, maxResults, null); - } - - /** - * Creates geocoder with default Locale. - * - * @see ReactiveLocationProvider#getGeocodeObservable(String, int, LatLngBounds, Locale) - */ - public Observable> getGeocodeObservable(String locationName, int maxResults, LatLngBounds bounds) { - return getGeocodeObservable(locationName, maxResults, bounds, null); - } - - /** - * Creates observable that translates a street address or other description into a list of - * possible addresses using included Geocoder class. You should subscribe for this - * observable on I/O thread. - * The stream finishes after address list is available. - *

- * You may specify a bounding box for the search results. - * - * @param locationName a user-supplied description of a location - * @param maxResults max number of results you are interested in - * @param bounds restricts the results to geographical bounds. May be null - * @param locale locale passed to geocoder - * @return observable that serves list of address based on location name - */ - public Observable> getGeocodeObservable(String locationName, int maxResults, LatLngBounds bounds, Locale locale) { - return GeocodeObservable.createObservable(ctx.getContext(), factory, locationName, maxResults, bounds, locale); - } - - /** - * Creates observable that adds request and completes when the action is done. - *

- * Observable can report {@link pl.charmas.android.reactivelocation2.observables.GoogleAPIConnectionException} - * when there are trouble connecting with Google Play Services. - *

- * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. - *

- * Other exceptions will be reported that can be thrown on {@link com.google.android.gms.location.GeofencingApi#addGeofences(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.GeofencingRequest, android.app.PendingIntent)} - * - * @param geofenceTransitionPendingIntent pending intent to register on geofence transition - * @param request list of request to add - * @return observable that adds request - */ - @RequiresPermission("android.permission.ACCESS_FINE_LOCATION") - public Observable addGeofences(PendingIntent geofenceTransitionPendingIntent, GeofencingRequest request) { - return AddGeofenceObservableOnSubscribe.createObservable(ctx, factory, request, geofenceTransitionPendingIntent); - } - - /** - * Observable that can be used to remove geofences from LocationClient. - *

- * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. - *

- * Other exceptions will be reported that can be thrown on {@link com.google.android.gms.location.GeofencingApi#removeGeofences(com.google.android.gms.common.api.GoogleApiClient, android.app.PendingIntent)}. - *

- * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. - * - * @param pendingIntent key of registered geofences - * @return observable that removed geofences - */ - public Observable removeGeofences(PendingIntent pendingIntent) { - return RemoveGeofenceObservableOnSubscribe.createObservable(ctx, factory, pendingIntent); - } - - /** - * Observable that can be used to remove geofences from LocationClient. - *

- * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. - *

- * Other exceptions will be reported that can be thrown on {@link com.google.android.gms.location.GeofencingApi#removeGeofences(com.google.android.gms.common.api.GoogleApiClient, java.util.List)}. - *

- * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. - * - * @param requestIds geofences to remove - * @return observable that removed geofences - */ - public Observable removeGeofences(List requestIds) { - return RemoveGeofenceObservableOnSubscribe.createObservable(ctx, factory, requestIds); - } - - - /** - * Observable that can be used to observe activity provided by Actity Recognition mechanism. - * - * @param detectIntervalMiliseconds detecion interval - * @return observable that provides activity recognition - */ - public Observable getDetectedActivity(int detectIntervalMiliseconds) { - return ActivityUpdatesObservableOnSubscribe.createObservable(ctx, factory, detectIntervalMiliseconds); - } - - /** - * Observable that can be used to check settings state for given location request. - * - * @param locationRequest location request - * @return observable that emits check result of location settings - * @see com.google.android.gms.location.SettingsApi - */ - public Observable checkLocationSettings(final LocationSettingsRequest locationRequest) { - return getGoogleApiClientObservable(LocationServices.API) - .flatMap(new Function>() { - @Override - public Observable apply(GoogleApiClient googleApiClient) { - return fromPendingResult(LocationServices.SettingsApi.checkLocationSettings(googleApiClient, locationRequest)); - } - }); - } - - /** - * Returns observable that fetches current place from Places API. To flatmap and auto release - * buffer to {@link com.google.android.gms.location.places.PlaceLikelihood} observable use - * {@link DataBufferObservable}. - * - * @param placeFilter filter - * @return observable that emits current places buffer and completes - */ - public Observable getCurrentPlace(@Nullable final PlaceFilter placeFilter) { - return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap(new Function>() { - @Override - public Observable apply(GoogleApiClient api) { - return fromPendingResult(Places.PlaceDetectionApi.getCurrentPlace(api, placeFilter)); - } - }); - } - - /** - * Returns observable that fetches a place from the Places API using the place ID. - * - * @param placeId id for place - * @return observable that emits places buffer and completes - */ - public Observable getPlaceById(@Nullable final String placeId) { - return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap(new Function>() { - @Override - public Observable apply(GoogleApiClient api) { - return fromPendingResult(Places.GeoDataApi.getPlaceById(api, placeId)); - } - }); - } - - /** - * Returns observable that fetches autocomplete predictions from Places API. To flatmap and autorelease - * {@link com.google.android.gms.location.places.AutocompletePredictionBuffer} you can use - * {@link DataBufferObservable}. - * - * @param query search query - * @param bounds bounds where to fetch suggestions from - * @param filter filter - * @return observable with suggestions buffer and completes - */ - public Observable getPlaceAutocompletePredictions(final String query, final LatLngBounds bounds, final AutocompleteFilter filter) { - return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap(new Function>() { - @Override - public Observable apply(GoogleApiClient api) { - return fromPendingResult(Places.GeoDataApi.getAutocompletePredictions(api, query, bounds, filter)); - } - }); - } - - /** - * Returns observable that fetches photo metadata from the Places API using the place ID. - * - * @param placeId id for place - * @return observable that emits metadata buffer and completes - */ - public Observable getPhotoMetadataById(final String placeId) { - return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap(new Function>() { - @Override - public Observable apply(GoogleApiClient api) { - return fromPendingResult(Places.GeoDataApi.getPlacePhotos(api, placeId)); - } - }); - } - - /** - * Returns observable that fetches a placePhotoMetadata from the Places API using the place placePhotoMetadata metadata. - * Use after fetching the place placePhotoMetadata metadata with {@link ReactiveLocationProvider#getPhotoMetadataById(String)} - * - * @param placePhotoMetadata the place photo meta data - * @return observable that emits the photo result and completes - */ - public Observable getPhotoForMetadata(final PlacePhotoMetadata placePhotoMetadata) { - return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) - .flatMap(new Function>() { - @Override - public Observable apply(GoogleApiClient api) { - return fromPendingResult(placePhotoMetadata.getPhoto(api)); - } - }); - } - - /** - * Observable that emits {@link com.google.android.gms.common.api.GoogleApiClient} object after connection. - * In case of error {@link pl.charmas.android.reactivelocation2.observables.GoogleAPIConnectionException} is emmited. - * When connection to Google Play Services is suspended {@link pl.charmas.android.reactivelocation2.observables.GoogleAPIConnectionSuspendedException} - * is emitted as error. - * Do not disconnect from apis client manually - just unsubscribe. - * - * @param apis collection of apis to connect to - * @return observable that emits apis client after successful connection - */ - public Observable getGoogleApiClientObservable(Api... apis) { - //noinspection unchecked - return GoogleAPIClientObservableOnSubscribe.create(ctx, factory, apis); - } - - /** - * Util method that wraps {@link com.google.android.gms.common.api.PendingResult} in Observable. - * - * @param result pending result to wrap - * @param parameter type of result - * @return observable that emits pending result and completes - */ - public static Observable fromPendingResult(PendingResult result) { - return Observable.create(new PendingResultObservableOnSubscribe<>(result)); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt new file mode 100644 index 00000000..1b3d71cf --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.kt @@ -0,0 +1,533 @@ +package pl.charmas.android.reactivelocation2 + +import android.annotation.SuppressLint +import android.app.PendingIntent +import android.content.Context +import android.location.Address +import android.location.Criteria +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import android.os.Bundle +import android.util.Log +import androidx.annotation.RequiresPermission +import com.google.android.gms.common.api.Api +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.ActivityRecognitionResult +import com.google.android.gms.location.GeofencingRequest +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationServices +import com.google.android.gms.location.LocationSettingsRequest +import com.google.android.gms.location.LocationSettingsResponse +import com.google.android.gms.maps.model.LatLngBounds +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Scheduler +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposables +import pl.charmas.android.reactivelocation2.ext.calldownOrEmpty +import pl.charmas.android.reactivelocation2.ext.onSuccessOrComplete +import pl.charmas.android.reactivelocation2.ext.reduceRightDefault +import pl.charmas.android.reactivelocation2.ext.toMaybe +import pl.charmas.android.reactivelocation2.observables.GoogleAPIClientMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.MaybeContext +import pl.charmas.android.reactivelocation2.observables.MaybeFactory +import pl.charmas.android.reactivelocation2.observables.ObservableContext +import pl.charmas.android.reactivelocation2.observables.ObservableFactory +import pl.charmas.android.reactivelocation2.observables.activity.ActivityUpdatesObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.geocode.GeocodeMaybe +import pl.charmas.android.reactivelocation2.observables.geocode.ReverseGeocodeObservable +import pl.charmas.android.reactivelocation2.observables.geofence.AddGeofenceMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.geofence.RemoveGeofenceMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.location.AddLocationIntentUpdatesMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.location.LocationUpdatesObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.location.MockLocationObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.location.RemoveLocationIntentUpdatesObservableOnSubscribe +import java.util.Locale +import java.util.concurrent.TimeUnit + +/** + * Factory of observables that can manipulate location + * delivered by Google Play Services. + */ +class ReactiveLocationProvider +/** + * Create location provider with default configuration or with given [ReactiveLocationProviderConfiguration] + * + * @param context preferably application context + * @param configuration configuration instance + */ +constructor( + val context: Context, + private val apiKey: String, + configuration: ReactiveLocationProviderConfiguration = ReactiveLocationProviderConfiguration.builder() + .build(), +) { + + private val locationManager: LocationManager = + context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + private val ctxObservable: ObservableContext = ObservableContext(context, configuration) + private val ctxMaybe: MaybeContext = MaybeContext(context, configuration) + private val factoryObservable: ObservableFactory = ObservableFactory(ctxObservable) + private val factoryMaybe: MaybeFactory = MaybeFactory(ctxMaybe) + + private val settingsClient = LocationServices.getSettingsClient(context) + private val geofencingClient = LocationServices.getGeofencingClient(context) + private val fusedLocationProviderClient = + LocationServices.getFusedLocationProviderClient(context) + + fun isGpsEnabled(): Single { + return isProviderEnabled(LocationManager.GPS_PROVIDER) + } + + fun isProviderEnabled(provider: String): Single { + return Single.create { subscriber -> + if (subscriber.isDisposed) { + return@create + } + subscriber.onSuccess(locationManager.isProviderEnabled(provider)) + } + } + + /** + * Creates observable that obtains last known location and than completes. + * Delivered location is never null - when it is unavailable Observable completes without emitting + * any value. + *

+ * Observable can report {@link pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionException} + * when there are trouble connecting with Google Play Services and other exceptions that + * can be thrown on {@link com.google.android.gms.location.FusedLocationProviderApi#getLastLocation(com.google.android.gms.common.api.GoogleApiClient)}. + * Everything is delivered by {@link io.reactivex.Observer#onError(Throwable)}. + * + * @return observable that serves last know location + */ + @get:RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) + val lastKnownLocation: Maybe + get() { + return fusedLocationProviderClient.lastLocation + .toMaybe() + } + + /** + * Creates observable that obtains last known location from BestProvider and than completes. + * */ + @get:RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) + val locationBestProvider: Maybe + get() { + return Maybe.create { emitter -> + val bestProvider = locationManager.getBestProvider( + Criteria(), false + ) + + if (!emitter.isDisposed) + emitter.onSuccessOrComplete(bestProvider) + + } + .flatMap { provider -> + getLastKnownLocationFromProvider(provider) + } + } + + @SuppressLint("MissingPermission") + fun getLastKnownLocationFromProvider(provider: String): Maybe { + return Maybe.create { emitter -> + val locationListener = object : LocationListener { + override fun onLocationChanged(location: Location?) { + Log.d(TAG, "onLocationChanged: $location") + emitter.onSuccessOrComplete(location) + } + + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + Log.d(TAG, "onStatusChanged: $provider status = $status") + } + + override fun onProviderEnabled(provider: String?) { + Log.d(TAG, "onProviderEnabled: $provider") + } + + override fun onProviderDisabled(provider: String?) { + Log.d(TAG, "onProviderDisabled: $provider") + if (!emitter.isDisposed) + emitter.onError(RuntimeException("onProviderDisabled: $provider")) + } + } + + emitter.setDisposable(Disposables.fromAction { + locationManager.removeUpdates(locationListener) + }) + + locationManager.requestLocationUpdates( + provider, + 1000, + 1000f, + locationListener, + null + ) + + val location = locationManager.getLastKnownLocation(provider) + + if (location != null) { + emitter.onSuccess(location) + } + } + .subscribeOn(AndroidSchedulers.mainThread()) + } + + @SuppressLint("MissingPermission") + fun getLocationUpdatesFromProvider(provider: String): Observable { + return Observable.create { emitter -> + val locationListener = object : LocationListener { + override fun onLocationChanged(location: Location?) { + Log.d(TAG, "onLocationChanged: $location") + if (location != null) { + if (!emitter.isDisposed) { + emitter.onNext(location) + } + } + } + + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + Log.d(TAG, "onStatusChanged: $provider status = $status") + } + + override fun onProviderEnabled(provider: String?) { + Log.d(TAG, "onProviderEnabled: $provider") + } + + override fun onProviderDisabled(provider: String?) { + Log.d(TAG, "onProviderDisabled: $provider") + } + } + + emitter.setDisposable(Disposables.fromAction { + locationManager.removeUpdates(locationListener) + }) + + locationManager.requestLocationUpdates( + provider, + 1000, + 1000f, + locationListener, + null + ) + + val location = locationManager.getLastKnownLocation(provider) + + if (location != null) { + if (!emitter.isDisposed) { + emitter.onNext(location) + } + } + } + .subscribeOn(AndroidSchedulers.mainThread()) + } + + /** + * Creates observable that obtains best last known location from Providers and than completes. + * */ + @RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) + fun getLastKnownLocationFromAllProviders( + time: Int, + timeUnit: TimeUnit, + scheduler: Scheduler, + ): Maybe { + return Maybe.create> { emitter -> + emitter.onSuccessOrComplete(locationManager.allProviders) + }.flatMap { allProviders -> + Observable.fromIterable(allProviders) + .flatMapMaybe { + getLastKnownLocationFromProvider(it) + .onErrorResumeNext { throwable: Throwable -> Maybe.empty() } + .calldownOrEmpty(time, timeUnit, scheduler) + } + .toList() + .flatMapMaybe { locations -> + val bestLocation = + locations.reduceRightDefault(locations.firstOrNull()) { first, second -> + when { + first == null -> second + second == null -> first + second.accuracy > first.accuracy -> first + else -> second + } + } + if (bestLocation == null) { + Maybe.empty() + } else { + Maybe.just(bestLocation) + } + } + } + } + + /** + * Creates observable that allows to observe infinite stream of location updates. + * To stop the stream you have to unsubscribe from observable - location updates are + * then disconnected. + *

+ * Observable can report {@link pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionException} + * when there are trouble connecting with Google Play Services and other exceptions that + * can be thrown on {@link com.google.android.gms.location.FusedLocationProviderApi#requestLocationUpdates(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.LocationRequest, com.google.android.gms.location.LocationListener)}. + * Everything is delivered by {@link io.reactivex.Observer#onError(Throwable)}. + * + * @param locationRequest request object with info about what kind of location you need + * @return observable that serves infinite stream of location updates + */ + @RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) + fun getUpdatedLocation(locationRequest: LocationRequest): Observable { + return LocationUpdatesObservableOnSubscribe.createObservable( + fusedLocationProviderClient, + ctxObservable, + factoryObservable, + locationRequest + ) + } + + /** + * Returns an observable which activates mock location mode when subscribed to, using the + * supplied observable as a source of mock locations. Mock locations will replace normal + * location information for all users of the FusedLocationProvider API on the device while this + * observable is subscribed to. + *

+ * To use this method, mock locations must be enabled in developer options and your application + * must hold the android.permission.ACCESS_MOCK_LOCATION permission, or a {@link java.lang.SecurityException} + * will be thrown. + *

+ * All statuses that are not successful will be reported as {@link pl.charmas.android.reactivelocation2.observables.StatusException}. + *

+ * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. + * + * @param sourceLocationObservable observable that emits {@link android.location.Location} instances suitable to use as mock locations + * @return observable that emits {@link com.google.android.gms.common.api.Status} + */ + @RequiresPermission(allOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_MOCK_LOCATION"]) + fun mockLocation(sourceLocationObservable: Observable): Observable { + return MockLocationObservableOnSubscribe.create( + fusedLocationProviderClient, + ctxObservable, + factoryObservable, + sourceLocationObservable + ) + } + + /** + * Creates an observable that adds a {@link android.app.PendingIntent} as a location listener. + *

+ * This invokes {@link com.google.android.gms.location.FusedLocationProviderApi#requestLocationUpdates(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.LocationRequest, android.app.PendingIntent)}. + *

+ * When location updates are no longer required, a call to {@link #removeLocationUpdates(android.app.PendingIntent)} + * should be made. + *

+ * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. + * + * @param locationRequest request object with info about what kind of location you need + * @param intent PendingIntent that will be called with location updates + * @return observable that adds the request and PendingIntent + */ + @RequiresPermission(anyOf = ["android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"]) + fun requestLocationUpdates( + locationRequest: LocationRequest, + intent: PendingIntent, + ): Maybe { + return AddLocationIntentUpdatesMaybeOnSubscribe.create( + fusedLocationProviderClient, + ctxMaybe, + factoryMaybe, + locationRequest, + intent + ) + } + + /** + * Observable that can be used to remove {@link android.app.PendingIntent} location updates. + *

+ * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. + * + * @param intent PendingIntent to remove location updates for + * @return observable that removes the PendingIntent + */ + fun removeLocationUpdates(intent: PendingIntent): Maybe { + return RemoveLocationIntentUpdatesObservableOnSubscribe.create( + fusedLocationProviderClient, + ctxMaybe, + factoryMaybe, + intent + ) + } + + /** + * Creates observable that translates latitude and longitude to list of possible addresses using + * included Geocoder class. In case geocoder fails with IOException("Service not Available") fallback + * decoder is used using google web api. You should subscribe for this observable on I/O thread. + * The stream finishes after address list is available. + * + * @param locale locale for address language + * @param lat latitude + * @param lng longitude + * @param maxResults maximal number of results you are interested in + * @return observable that serves list of address based on location + */ + fun getReverseGeocodeMaybe( + locale: Locale = Locale.getDefault(), + lat: Double, + lng: Double, + maxResults: Int, + ): Maybe> { + return ReverseGeocodeObservable.create( + ctxObservable.context, + apiKey, + factoryMaybe, + locale, + lat, + lng, + maxResults + ) + } + + /** + * Creates observable that translates a street address or other description into a list of + * possible addresses using included Geocoder class. You should subscribe for this + * observable on I/O thread. + * The stream finishes after address list is available. + *

+ * You may specify a bounding box for the search results. + * + * @param locationName a user-supplied description of a location + * @param maxResults max number of results you are interested in + * @param bounds restricts the results to geographical bounds. May be null + * @param locale locale passed to geocoder + * @return observable that serves list of address based on location name + */ + fun getGeocodeObservable( + locationName: String, + maxResults: Int, + bounds: LatLngBounds? = null, + locale: Locale? = null, + ): Maybe> { + return GeocodeMaybe.create( + ctxObservable.context, + factoryMaybe, + locationName, + maxResults, + bounds, + locale ?: Locale.getDefault() + ) + } + + /** + * Creates observable that adds request and completes when the action is done. + *

+ * Observable can report {@link pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionException} + * when there are trouble connecting with Google Play Services. + *

+ * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. + *

+ * Other exceptions will be reported that can be thrown on {@link com.google.android.gms.location.GeofencingApi#addGeofences(com.google.android.gms.common.api.GoogleApiClient, com.google.android.gms.location.GeofencingRequest, android.app.PendingIntent)} + * + * @param geofenceTransitionPendingIntent pending intent to register on geofence transition + * @param request list of request to add + * @return observable that adds request + */ + @RequiresPermission("android.permission.ACCESS_FINE_LOCATION") + fun addGeofences( + geofenceTransitionPendingIntent: PendingIntent, + request: GeofencingRequest, + ): Maybe { + return AddGeofenceMaybeOnSubscribe.createMaybe( + geofencingClient, + ctxMaybe, + factoryMaybe, + request, + geofenceTransitionPendingIntent + ) + } + + /** + * Observable that can be used to remove geofences from LocationClient. + *

+ * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. + *

+ * Other exceptions will be reported that can be thrown on {@link com.google.android.gms.location.GeofencingApi#removeGeofences(com.google.android.gms.common.api.GoogleApiClient, android.app.PendingIntent)}. + *

+ * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. + * + * @param pendingIntent key of registered geofences + * @return observable that removed geofences + */ + fun removeGeofences(pendingIntent: PendingIntent): Maybe { + return RemoveGeofenceMaybeOnSubscribe.createMaybe( + ctxMaybe, + factoryMaybe, + pendingIntent, geofencingClient + ) + } + + /** + * Observable that can be used to remove geofences from LocationClient. + *

+ * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. + *

+ * Other exceptions will be reported that can be thrown on {@link com.google.android.gms.location.GeofencingApi#removeGeofences(com.google.android.gms.common.api.GoogleApiClient, java.util.List)}. + *

+ * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. + * + * @param requestIds geofences to remove + * @return observable that removed geofences + */ + fun removeGeofences(requestIds: List): Maybe { + return RemoveGeofenceMaybeOnSubscribe.createMaybe( + ctxMaybe, + factoryMaybe, + requestIds, + geofencingClient + ) + } + + /** + * Observable that can be used to observe activity provided by Activity Recognition mechanism. + * + * @param detectIntervalMiliseconds detecion interval + * @return observable that provides activity recognition + */ + fun getDetectedActivity(detectIntervalMiliseconds: Int): Observable { + return ActivityUpdatesObservableOnSubscribe.createObservable( + ctxObservable, + factoryObservable, + detectIntervalMiliseconds + ) + } + + /** + * Observable that can be used to check settings state for given location request. + * + * @param locationRequest location request + * @return observable that emits check result of location settings + * @see com.google.android.gms.location.SettingsApi + */ + fun checkLocationSettings( + locationRequest: LocationSettingsRequest, + ): Maybe { + return settingsClient + .checkLocationSettings(locationRequest) + .toMaybe() + } + + /** + * Observable that emits {@link com.google.android.gms.common.api.GoogleApiClient} object after connection. + * In case of error {@link pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionException} is emmited. + * When connection to Google Play Services is suspended {@link pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionSuspendedException} + * is emitted as error. + * Do not disconnect from apis client manually - just unsubscribe. + * + * @param apis collection of apis to connect to + * @return observable that emits apis client after successful connection + */ + private fun getGoogleApiClientMaybe(vararg apis: Api): Maybe { + return GoogleAPIClientMaybeOnSubscribe.create(ctxMaybe, factoryMaybe, *apis) + } + + companion object { + const val TAG: String = "ReactiveLocationProvide" + } +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.java deleted file mode 100644 index 2cbfe264..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.java +++ /dev/null @@ -1,69 +0,0 @@ -package pl.charmas.android.reactivelocation2; - -import android.os.Handler; -import android.support.annotation.Nullable; - -/** - * Configuration for location provider. Pleas use builder to create an instance. - */ -public class ReactiveLocationProviderConfiguration { - private final Handler customCallbackHandler; - private final boolean retryOnConnectionSuspended; - - private ReactiveLocationProviderConfiguration(Builder builder) { - this.customCallbackHandler = builder.customCallbackHandler; - this.retryOnConnectionSuspended = builder.retryOnConnectionSuspended; - } - - public Handler getCustomCallbackHandler() { - return customCallbackHandler; - } - - public boolean isRetryOnConnectionSuspended() { - return retryOnConnectionSuspended; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private Handler customCallbackHandler = null; - private boolean retryOnConnectionSuspended = false; - - /** - * Allows to set custom handler on which all Google Play Services callbacks are called. - *

- * Default: null - * - * @param customCallbackHandler handler instance - * @return builder instance - */ - public Builder setCustomCallbackHandler(@Nullable Handler customCallbackHandler) { - this.customCallbackHandler = customCallbackHandler; - return this; - } - - /** - * Property that allows automatic retries of connection to Google Play Services when it has bean suspended. - *

- * Default: false - * - * @param retryOnConnectionSuspended if should we retry on connection failure - * @return builder instance - */ - public Builder setRetryOnConnectionSuspended(boolean retryOnConnectionSuspended) { - this.retryOnConnectionSuspended = retryOnConnectionSuspended; - return this; - } - - /** - * Builds configuration instance - * - * @return configuration instance - */ - public ReactiveLocationProviderConfiguration build() { - return new ReactiveLocationProviderConfiguration(this); - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.kt new file mode 100644 index 00000000..0af88f26 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.kt @@ -0,0 +1,67 @@ +package pl.charmas.android.reactivelocation2 + +import android.os.Handler + +/** + * Configuration for location provider. Pleas use builder to create an instance. + */ +class ReactiveLocationProviderConfiguration private constructor(builder: Builder) { + + val customCallbackHandler: Handler? = builder.customCallbackHandler + val isRetryOnConnectionSuspended: Boolean = builder.retryOnConnectionSuspended + + class Builder { + var customCallbackHandler: Handler? = null + private set(value) { + field = value + } + var retryOnConnectionSuspended = false + private set(value) { + field = value + } + + /** + * Allows to set custom handler on which all Google Play Services callbacks are called. + * + * + * Default: null + * + * @param customCallbackHandler handler instance + * @return builder instance + */ + fun setCustomCallbackHandler(customCallbackHandler: Handler?): Builder { + this.customCallbackHandler = customCallbackHandler + return this + } + + /** + * Property that allows automatic retries of connection to Google Play Services when it has bean suspended. + * + * + * Default: false + * + * @param retryOnConnectionSuspended if should we retry on connection failure + * @return builder instance + */ + fun setRetryOnConnectionSuspended(retryOnConnectionSuspended: Boolean): Builder { + this.retryOnConnectionSuspended = retryOnConnectionSuspended + return this + } + + /** + * Builds configuration instance + * + * @return configuration instance + */ + fun build(): ReactiveLocationProviderConfiguration { + return ReactiveLocationProviderConfiguration(this) + } + } + + companion object { + @JvmStatic + fun builder(): Builder { + return Builder() + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactivePlacesProvider.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactivePlacesProvider.kt new file mode 100644 index 00000000..8bcdadc0 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactivePlacesProvider.kt @@ -0,0 +1,173 @@ +package pl.charmas.android.reactivelocation2 + +import android.content.Context +import android.graphics.Bitmap +import androidx.annotation.IntRange +import androidx.annotation.RequiresPermission +import com.google.android.libraries.places.api.Places +import com.google.android.libraries.places.api.model.AutocompletePrediction +import com.google.android.libraries.places.api.model.PhotoMetadata +import com.google.android.libraries.places.api.model.Place +import com.google.android.libraries.places.api.net.FetchPhotoRequest +import com.google.android.libraries.places.api.net.FetchPlaceRequest +import com.google.android.libraries.places.api.net.FetchPlaceResponse +import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest +import com.google.android.libraries.places.api.net.FindAutocompletePredictionsResponse +import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest +import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse +import io.reactivex.Maybe +import io.reactivex.Observable +import pl.charmas.android.reactivelocation2.ext.errorSkip +import pl.charmas.android.reactivelocation2.ext.mapOrEmpty +import pl.charmas.android.reactivelocation2.ext.toMaybe +import java.util.Collections + +class ReactivePlacesProvider constructor( + val context: Context, +) { + + private val placesClient = Places.createClient(context) + + /** + * Returns observable that fetches current place from Places API. To flatmap and auto release + * buffer to {@link com.google.android.gms.location.places.PlaceLikelihood} observable use + * {@link DataBufferObservable}. + * + * @param placeFilter filter + * @return observable that emits current places buffer and completes + */ + @RequiresPermission(allOf = ["android.permission.ACCESS_FINE_LOCATION", "android.permission.ACCESS_WIFI_STATE"]) + fun getCurrentPlace(placeFilter: FindCurrentPlaceRequest): Maybe { + return placesClient.findCurrentPlace(placeFilter) + .toMaybe() + } + + /** + * Returns observable that fetches autocomplete predictions from Places API. To flatmap and autorelease + * {@link com.google.android.gms.location.places.AutocompletePredictionBuffer} you can use + * {@link DataBufferObservable}. + * + * @param query search query + * @param bounds bounds where to fetch suggestions from + * @param filter filter + * @return observable with suggestions buffer and completes + * + * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatAutocompletePredictions(java.lang.String, com.google.android.gms.maps.model.LatLngBounds, com.google.android.libraries.places.compat.AutocompleteFilter)} + */ + fun getPlaceAutocompletePredictions(result: FindAutocompletePredictionsRequest): Maybe> { + return placesClient + .findAutocompletePredictions(result) + .toMaybe() + .map { obj: FindAutocompletePredictionsResponse -> obj.autocompletePredictions } + } + + /** + * Returns observable that fetches photo metadata from the Places API using the place ID. + * + * @param placeId id for place + * @return observable that emits metadata buffer and completes + */ + fun getPhotosByPlaceId( + placeId: String, + @IntRange(from = 1L, to = 1600L) height: Int, + @IntRange(from = 1L, to = 1600L) width: Int, + ): Maybe> { + return getPlaceById(placeId, Collections.singletonList(Place.Field.PHOTO_METADATAS)) + .mapOrEmpty { it.place.photoMetadatas } + .filter { it.isNotEmpty() } + .flatMapSingle { photoMetadatas -> + Observable.fromIterable(photoMetadatas) + .flatMapMaybe { photoMetadata -> + placesClient + .fetchPhoto( + FetchPhotoRequest.builder( + photoMetadata + ) + .setMaxHeight(height) + .setMaxWidth(width) + .build() + ) + .toMaybe() + .errorSkip() + } + .map { it.bitmap } + .toList() + } + .filter { it.isNotEmpty() } + } + /** + * Returns observable that fetches photo metadata from the Places API using the place ID. + * + * @param placeId id for place + * @return observable that emits metadata buffer and completes + */ + fun getFirstPhotoByPlaceId( + placeId: String, + @IntRange(from = 1L, to = 1600L) height: Int, + @IntRange(from = 1L, to = 1600L) width: Int, + ): Maybe { + return getPlaceById(placeId, Collections.singletonList(Place.Field.PHOTO_METADATAS)) + .mapOrEmpty { it.place.photoMetadatas?.firstOrNull() } + .flatMap { photoMetadata -> + placesClient + .fetchPhoto( + FetchPhotoRequest.builder( + photoMetadata + ) + .setMaxHeight(height) + .setMaxWidth(width) + .build() + ) + .toMaybe() + } + .map { it.bitmap } + } + + fun getPlaceById( + placeId: String, + fields: List, + ): Maybe { + return placesClient + .fetchPlace( + FetchPlaceRequest.newInstance( + placeId, + fields + ) + ) + .toMaybe() + } + + /** + * Returns observable that fetches a placePhotoMetadata from the Places API using the place placePhotoMetadata metadata. + * Use after fetching the place placePhotoMetadata metadata with [ReactiveLocationProvider.getPhotoMetadataById] + * + * @param placePhotoMetadata the place photo meta data + * @return observable that emits the photo result and completes + */ + fun getPhotoForMetadata(placePhotoMetadata: PhotoMetadata): Maybe { + return placesClient + .fetchPhoto( + FetchPhotoRequest.builder(placePhotoMetadata) + .build() + ) + .toMaybe() + .map { it.bitmap } + } + + /** + * Returns observable that fetches a place from the Places API using the place ID. + * + * @param placeId id for place + * @return observable that emits places buffer and completes + * + * @deprecated use {@link ReactiveLocationProvider#getPlaceCompatById(java.lang.String)} + */ + fun getPlaceById(placeId: String): Maybe { + val listOf = listOf( + Place.Field.ID, + Place.Field.LAT_LNG, + Place.Field.ADDRESS + ) + return getPlaceById(placeId, listOf) + } +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/ListExt.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/ListExt.kt new file mode 100644 index 00000000..6d09f992 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/ListExt.kt @@ -0,0 +1,6 @@ +package pl.charmas.android.reactivelocation2.ext + +inline fun List.reduceRightDefault(defaultIfEmpty: T, operation: (T, acc: T) -> T): T { + return if (isEmpty()) defaultIfEmpty + else reduceRight(operation) +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/MaybeExt.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/MaybeExt.kt new file mode 100644 index 00000000..8749f0ee --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/MaybeExt.kt @@ -0,0 +1,39 @@ +package pl.charmas.android.reactivelocation2.ext + +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import io.reactivex.Scheduler +import java.util.concurrent.TimeUnit + +fun MaybeEmitter.onSuccessOrComplete(item: T?) { + if (isDisposed) { + return + } + item?.let { onSuccess(it) } ?: onComplete() +} + +fun Maybe.calldownOrEmpty(time: Int, timeUnit: TimeUnit, scheduler: Scheduler, id: String? = null): Maybe { + return this.compose { source -> + Maybe.merge( + source.map { it.asOptional() }, + Maybe.timer(time.toLong(), timeUnit, scheduler).mapOrEmpty { Optional.empty() } + ) + .firstElement() + } + .mapOrEmpty { it.value } +} + +fun Maybe.mapOrEmpty(mapper: (T) -> R?): Maybe { + return this.flatMap { item -> + mapper(item)?.let { Maybe.just(it) } ?: Maybe.empty() + } +} + +fun Maybe.errorSkip(printStackTrace: Boolean = false): Maybe { + return this.onErrorResumeNext { throwable: Throwable -> + if (printStackTrace) { + throwable.printStackTrace() + } + Maybe.empty() + } +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/OptionalKt.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/OptionalKt.kt new file mode 100644 index 00000000..312ea2d3 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/OptionalKt.kt @@ -0,0 +1,21 @@ +package pl.charmas.android.reactivelocation2.ext + +data class Optional(val value: T? = null) { + + companion object { + fun empty(): Optional { + return Optional() + } + } + + fun isEmpty(): Boolean { + return value == null + } + + fun isNotEmpty(): Boolean { + return value != null + } + +} + +fun T?.asOptional() = Optional(this) diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt new file mode 100644 index 00000000..6f9ca31a --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ext/TasksExt.kt @@ -0,0 +1,9 @@ +package pl.charmas.android.reactivelocation2.ext + +import com.google.android.gms.tasks.Task +import io.reactivex.Maybe +import pl.charmas.android.reactivelocation2.observables.TaskResultMaybeOnSubscribe + +fun Task.toMaybe(): Maybe { + return Maybe.create(TaskResultMaybeOnSubscribe(this)) +} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationMaybeOnSubscribe.kt new file mode 100644 index 00000000..e933a25b --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationMaybeOnSubscribe.kt @@ -0,0 +1,6 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.location.LocationServices + +abstract class BaseLocationMaybeOnSubscribe protected constructor(ctx: MaybeContext) : + BaseMaybeOnSubscribe(ctx, LocationServices.API) \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.java deleted file mode 100644 index 86278213..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.java +++ /dev/null @@ -1,9 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import com.google.android.gms.location.LocationServices; - -public abstract class BaseLocationObservableOnSubscribe extends BaseObservableOnSubscribe { - protected BaseLocationObservableOnSubscribe(ObservableContext ctx) { - super(ctx, LocationServices.API); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.kt new file mode 100644 index 00000000..6a36916d --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.kt @@ -0,0 +1,6 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.location.LocationServices + +abstract class BaseLocationObservableOnSubscribe protected constructor(ctx: ObservableContext) : + BaseObservableOnSubscribe(ctx, LocationServices.API) \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.kt new file mode 100644 index 00000000..1d8eb9b5 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseMaybeOnSubscribe.kt @@ -0,0 +1,102 @@ +package pl.charmas.android.reactivelocation2.observables + +import android.content.Context +import android.os.Bundle +import android.os.Handler +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.api.Api +import com.google.android.gms.common.api.Api.ApiOptions.NotRequiredOptions +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks +import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener +import io.reactivex.MaybeEmitter +import io.reactivex.MaybeOnSubscribe +import io.reactivex.disposables.Disposables +import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionException +import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionSuspendedException + +abstract class BaseMaybeOnSubscribe @SafeVarargs protected constructor( + ctx: MaybeContext, + vararg services: Api +) : MaybeOnSubscribe { + + private val ctx: Context = ctx.context + private val handler: Handler? = ctx.handler + private val services: List> = listOf(*services) + + override fun subscribe(emitter: MaybeEmitter) { + val apiClient = createApiClient(emitter) + try { + apiClient.connect() + } catch (ex: Throwable) { + if (!emitter.isDisposed) { + emitter.onError(ex) + } + } + emitter.setDisposable(Disposables.fromAction { + onDisposed(apiClient) + apiClient.disconnect() + }) + } + + private fun createApiClient(emitter: MaybeEmitter): GoogleApiClient { + val apiClientConnectionCallbacks = ApiClientConnectionCallbacks(emitter) + + val builder = GoogleApiClient.Builder(ctx) + for (service in services) { + builder.addApi(service) + } + builder + .addConnectionCallbacks(apiClientConnectionCallbacks) + .addOnConnectionFailedListener(apiClientConnectionCallbacks) + if (handler != null) { + builder.setHandler(handler) + } + val apiClient = builder.build() + apiClientConnectionCallbacks.setClient(apiClient) + return apiClient + } + + private fun onDisposed(locationClient: GoogleApiClient) {} + + protected abstract fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: MaybeEmitter + ) + + private inner class ApiClientConnectionCallbacks constructor(private val emitter: MaybeEmitter) : + ConnectionCallbacks, OnConnectionFailedListener { + private lateinit var apiClient: GoogleApiClient + + override fun onConnected(bundle: Bundle?) { + try { + onGoogleApiClientReady(apiClient, emitter) + } catch (ex: Throwable) { + if (!emitter.isDisposed) { + emitter.onError(ex) + } + } + } + + override fun onConnectionSuspended(cause: Int) { + if (!emitter.isDisposed) { + emitter.onError(GoogleAPIConnectionSuspendedException(cause)) + } + } + + override fun onConnectionFailed(connectionResult: ConnectionResult) { + if (!emitter.isDisposed) { + emitter.onError( + GoogleAPIConnectionException( + "Error connecting to GoogleApiClient.", + connectionResult + ) + ) + } + } + + fun setClient(client: GoogleApiClient) { + apiClient = client + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.java deleted file mode 100644 index 3b7adf6e..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.java +++ /dev/null @@ -1,122 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import android.content.Context; -import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.NonNull; - -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.api.Api; -import com.google.android.gms.common.api.GoogleApiClient; - -import java.util.Arrays; -import java.util.List; - -import io.reactivex.ObservableEmitter; -import io.reactivex.ObservableOnSubscribe; -import io.reactivex.disposables.Disposables; -import io.reactivex.functions.Action; - - -public abstract class BaseObservableOnSubscribe implements ObservableOnSubscribe { - private final Context ctx; - private final Handler handler; - private final List> services; - - @SafeVarargs - protected BaseObservableOnSubscribe(ObservableContext ctx, Api... services) { - this.ctx = ctx.getContext(); - this.handler = ctx.getHandler(); - this.services = Arrays.asList(services); - } - - @Override - public void subscribe(ObservableEmitter emitter) throws Exception { - final GoogleApiClient apiClient = createApiClient(emitter); - try { - apiClient.connect(); - } catch (Throwable ex) { - if (!emitter.isDisposed()) { - emitter.onError(ex); - } - } - - emitter.setDisposable(Disposables.fromAction(new Action() { - @Override - public void run() throws Exception { - onDisposed(apiClient); - apiClient.disconnect(); - } - })); - } - - - private GoogleApiClient createApiClient(ObservableEmitter emitter) { - ApiClientConnectionCallbacks apiClientConnectionCallbacks = new ApiClientConnectionCallbacks(emitter); - GoogleApiClient.Builder apiClientBuilder = new GoogleApiClient.Builder(ctx); - - for (Api service : services) { - apiClientBuilder = apiClientBuilder.addApi(service); - } - - apiClientBuilder = apiClientBuilder - .addConnectionCallbacks(apiClientConnectionCallbacks) - .addOnConnectionFailedListener(apiClientConnectionCallbacks); - - if (this.handler != null) { - apiClientBuilder = apiClientBuilder.setHandler(handler); - } - - GoogleApiClient apiClient = apiClientBuilder.build(); - apiClientConnectionCallbacks.setClient(apiClient); - return apiClient; - } - - protected void onDisposed(GoogleApiClient locationClient) { - } - - protected abstract void onGoogleApiClientReady(GoogleApiClient apiClient, ObservableEmitter emitter); - - private class ApiClientConnectionCallbacks implements - GoogleApiClient.ConnectionCallbacks, - GoogleApiClient.OnConnectionFailedListener { - - final private ObservableEmitter emitter; - - private GoogleApiClient apiClient; - - private ApiClientConnectionCallbacks(ObservableEmitter emitter) { - this.emitter = emitter; - } - - @Override - public void onConnected(Bundle bundle) { - try { - onGoogleApiClientReady(apiClient, emitter); - } catch (Throwable ex) { - if (!emitter.isDisposed()) { - emitter.onError(ex); - } - } - } - - @Override - public void onConnectionSuspended(int cause) { - if (!emitter.isDisposed()) { - emitter.onError(new GoogleAPIConnectionSuspendedException(cause)); - } - } - - @Override - public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { - if (!emitter.isDisposed()) { - emitter.onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient.", - connectionResult)); - } - } - - void setClient(GoogleApiClient client) { - this.apiClient = client; - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.kt new file mode 100644 index 00000000..24802709 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.kt @@ -0,0 +1,107 @@ +package pl.charmas.android.reactivelocation2.observables + +import android.content.Context +import android.os.Bundle +import android.os.Handler +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.api.Api +import com.google.android.gms.common.api.Api.ApiOptions.NotRequiredOptions +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks +import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener +import io.reactivex.ObservableEmitter +import io.reactivex.ObservableOnSubscribe +import io.reactivex.disposables.Disposables +import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionException +import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionSuspendedException +import java.util.Arrays + +abstract class BaseObservableOnSubscribe @SafeVarargs protected constructor( + ctx: ObservableContext, + vararg services: Api +) : ObservableOnSubscribe { + + private val ctx: Context= ctx.context + private val handler: Handler? = ctx.handler + private val services: List> = listOf(*services) + + override fun subscribe(emitter: ObservableEmitter) { + val apiClient = createApiClient(emitter) + try { + apiClient.connect() + } catch (ex: Throwable) { + if (!emitter.isDisposed) { + emitter.onError(ex) + } + } + emitter.setDisposable(Disposables.fromAction { + onDisposed(apiClient) + apiClient.disconnect() + }) + } + + private fun createApiClient(emitter: ObservableEmitter): GoogleApiClient { + val apiClientConnectionCallbacks = + ApiClientConnectionCallbacks(emitter) + var apiClientBuilder = GoogleApiClient.Builder(ctx) + for (service in services) { + apiClientBuilder = apiClientBuilder.addApi(service) + } + apiClientBuilder = apiClientBuilder + .addConnectionCallbacks(apiClientConnectionCallbacks) + .addOnConnectionFailedListener(apiClientConnectionCallbacks) + if (handler != null) { + apiClientBuilder = apiClientBuilder.setHandler(handler) + } + val apiClient = apiClientBuilder.build() + apiClientConnectionCallbacks.setClient(apiClient) + return apiClient + } + + protected open fun onDisposed(locationClient: GoogleApiClient) {} + protected abstract fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: ObservableEmitter + ) + + private inner class ApiClientConnectionCallbacks constructor(private val emitter: ObservableEmitter) : + ConnectionCallbacks, OnConnectionFailedListener { + private lateinit var apiClient: GoogleApiClient + override fun onConnected(bundle: Bundle?) { + try { + onGoogleApiClientReady(apiClient, emitter) + } catch (ex: Throwable) { + if (!emitter.isDisposed) { + emitter.onError(ex) + } + } + } + + override fun onConnectionSuspended(cause: Int) { + if (!emitter.isDisposed) { + emitter.onError(GoogleAPIConnectionSuspendedException(cause)) + } + } + + override fun onConnectionFailed(connectionResult: ConnectionResult) { + if (!emitter.isDisposed) { + emitter.onError( + GoogleAPIConnectionException( + "Error connecting to GoogleApiClient.", + connectionResult + ) + ) + } + } + + fun setClient(client: GoogleApiClient) { + apiClient = client + } + } + + init { + this.ctx + handler + this.services + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt new file mode 100644 index 00000000..4caf52bb --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientMaybeOnSubscribe.kt @@ -0,0 +1,35 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.common.api.Api +import com.google.android.gms.common.api.Api.ApiOptions.NotRequiredOptions +import com.google.android.gms.common.api.GoogleApiClient +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter + +class GoogleAPIClientMaybeOnSubscribe @SafeVarargs private constructor( + ctx: MaybeContext, + vararg apis: Api +) : BaseMaybeOnSubscribe(ctx, *apis) { + + override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + if (!emitter.isDisposed) { + emitter.onSuccess(apiClient) + } + } + + companion object { + @SafeVarargs + fun create( + context: MaybeContext, + factory: MaybeFactory, + vararg apis: Api + ): Maybe { + return factory.create( + GoogleAPIClientMaybeOnSubscribe(context, *apis) + ) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientObservableOnSubscribe.java deleted file mode 100644 index 2a09d33e..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientObservableOnSubscribe.java +++ /dev/null @@ -1,26 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import com.google.android.gms.common.api.Api; -import com.google.android.gms.common.api.GoogleApiClient; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; - -public class GoogleAPIClientObservableOnSubscribe extends BaseObservableOnSubscribe { - - @SafeVarargs - public static Observable create(ObservableContext context, ObservableFactory factory, Api... apis) { - return factory.createObservable(new GoogleAPIClientObservableOnSubscribe(context, apis)); - } - - @SafeVarargs - private GoogleAPIClientObservableOnSubscribe(ObservableContext ctx, Api... apis) { - super(ctx, apis); - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, ObservableEmitter emitter) { - if (emitter.isDisposed()) return; - emitter.onNext(apiClient); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionException.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionException.java deleted file mode 100644 index 014070e5..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionException.java +++ /dev/null @@ -1,16 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import com.google.android.gms.common.ConnectionResult; - -public class GoogleAPIConnectionException extends RuntimeException { - private final ConnectionResult connectionResult; - - GoogleAPIConnectionException(String detailMessage, ConnectionResult connectionResult) { - super(detailMessage); - this.connectionResult = connectionResult; - } - - public ConnectionResult getConnectionResult() { - return connectionResult; - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionSuspendedException.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionSuspendedException.java deleted file mode 100644 index eeed3412..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionSuspendedException.java +++ /dev/null @@ -1,13 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -public class GoogleAPIConnectionSuspendedException extends RuntimeException { - private final int cause; - - GoogleAPIConnectionSuspendedException(int cause) { - this.cause = cause; - } - - public int getErrorCause() { - return cause; - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.kt new file mode 100644 index 00000000..4e7337dc --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeContext.kt @@ -0,0 +1,13 @@ +package pl.charmas.android.reactivelocation2.observables + +import android.content.Context +import android.os.Handler +import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration + +class MaybeContext constructor( + val context: Context, + configuration: ReactiveLocationProviderConfiguration +) { + val handler: Handler? = configuration.customCallbackHandler + val isRetryOnConnectionSuspended: Boolean = configuration.isRetryOnConnectionSuspended +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeEmitterWrapper.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeEmitterWrapper.kt new file mode 100644 index 00000000..1ab083b9 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeEmitterWrapper.kt @@ -0,0 +1,32 @@ +package pl.charmas.android.reactivelocation2.observables + +import io.reactivex.MaybeEmitter +import io.reactivex.MaybeObserver +import io.reactivex.disposables.Disposable + +class MaybeEmitterWrapper(private val emitter: MaybeEmitter) : + MaybeObserver { + override fun onSubscribe(d: Disposable) {} + + override fun onError(e: Throwable) { + if (!emitter.isDisposed) { + emitter.onError(e) + } + } + + override fun onComplete() { + if (!emitter.isDisposed) { + emitter.onComplete() + } + } + + override fun onSuccess(value: T) { + if (!emitter.isDisposed) { + if (value != null) { + emitter.onSuccess(value) + }else{ + emitter.onComplete() + } + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.kt new file mode 100644 index 00000000..ea5ba87d --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/MaybeFactory.kt @@ -0,0 +1,34 @@ +package pl.charmas.android.reactivelocation2.observables + +import io.reactivex.Maybe +import io.reactivex.MaybeOnSubscribe +import io.reactivex.MaybeSource +import io.reactivex.MaybeTransformer +import io.reactivex.functions.BiPredicate +import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionSuspendedException + +class MaybeFactory(private val context: MaybeContext) { + fun create(source: MaybeOnSubscribe?): Maybe { + return Maybe.create(source).compose( + RetryOnConnectionSuspension( + context.isRetryOnConnectionSuspended + ) + ) + } + + private class RetryOnConnectionSuspension internal constructor(private val shouldRetry: Boolean) : + MaybeTransformer { + override fun apply(upstream: Maybe): MaybeSource { + return if (shouldRetry) { + upstream.retry(IsConnectionSuspendedException()) + } else upstream + } + + private class IsConnectionSuspendedException : + BiPredicate { + override fun test(integer: Int, throwable: Throwable): Boolean { + return throwable is GoogleAPIConnectionSuspendedException + } + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.java deleted file mode 100644 index 4b66d820..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.java +++ /dev/null @@ -1,30 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import android.content.Context; -import android.os.Handler; - -import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration; - -public class ObservableContext { - private final Context context; - private final Handler handler; - private final boolean retryOnConnectionSuspended; - - public ObservableContext(Context context, ReactiveLocationProviderConfiguration configuration) { - this.context = context; - this.handler = configuration.getCustomCallbackHandler(); - this.retryOnConnectionSuspended = configuration.isRetryOnConnectionSuspended(); - } - - public Context getContext() { - return context; - } - - Handler getHandler() { - return handler; - } - - boolean isRetryOnConnectionSuspended() { - return retryOnConnectionSuspended; - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.kt new file mode 100644 index 00000000..ac90a86b --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.kt @@ -0,0 +1,13 @@ +package pl.charmas.android.reactivelocation2.observables + +import android.content.Context +import android.os.Handler +import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration + +class ObservableContext constructor( + val context: Context, + configuration: ReactiveLocationProviderConfiguration +) { + val handler: Handler? = configuration.customCallbackHandler + val isRetryOnConnectionSuspended: Boolean = configuration.isRetryOnConnectionSuspended +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.java deleted file mode 100644 index b7c9b8d1..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.java +++ /dev/null @@ -1,38 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import io.reactivex.ObservableEmitter; -import io.reactivex.Observer; -import io.reactivex.disposables.Disposable; - -public class ObservableEmitterWrapper implements Observer { - private final ObservableEmitter emitter; - - public ObservableEmitterWrapper(ObservableEmitter emitter) { - this.emitter = emitter; - } - - @Override - public void onSubscribe(Disposable d) { - } - - @Override - public void onNext(T t) { - if (!emitter.isDisposed()){ - emitter.onNext(t); - } - } - - @Override - public void onError(Throwable e) { - if (!emitter.isDisposed()) { - emitter.onError(e); - } - } - - @Override - public void onComplete() { - if (!emitter.isDisposed()) { - emitter.onComplete(); - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.kt new file mode 100644 index 00000000..3e5e7f6c --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.kt @@ -0,0 +1,29 @@ +package pl.charmas.android.reactivelocation2.observables + +import io.reactivex.ObservableEmitter +import io.reactivex.Observer +import io.reactivex.disposables.Disposable + +class ObservableEmitterWrapper(private val emitter: ObservableEmitter) : + Observer { + override fun onSubscribe(d: Disposable) {} + override fun onNext(value: T) { + if (!emitter.isDisposed) { + if (value != null) { + emitter.onNext(value) + } + } + } + + override fun onError(e: Throwable) { + if (!emitter.isDisposed) { + emitter.onError(e) + } + } + + override fun onComplete() { + if (!emitter.isDisposed) { + emitter.onComplete() + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.java deleted file mode 100644 index 3b1b734c..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import io.reactivex.Observable; -import io.reactivex.ObservableOnSubscribe; -import io.reactivex.ObservableSource; -import io.reactivex.ObservableTransformer; -import io.reactivex.functions.BiPredicate; - -public class ObservableFactory { - private final ObservableContext context; - - public ObservableFactory(ObservableContext context) { - this.context = context; - } - - public Observable createObservable(ObservableOnSubscribe source) { - return Observable.create(source).compose(new RetryOnConnectionSuspension(context.isRetryOnConnectionSuspended())); - } - - private static class RetryOnConnectionSuspension implements ObservableTransformer { - private final boolean shouldRetry; - - RetryOnConnectionSuspension(boolean shouldRetry) { - this.shouldRetry = shouldRetry; - } - - @Override - public ObservableSource apply(Observable upstream) { - if (shouldRetry) { - return upstream.retry(new IsConnectionSuspendedException()); - } - return upstream; - } - - private static class IsConnectionSuspendedException implements BiPredicate { - @Override - public boolean test(Integer integer, Throwable throwable) throws Exception { - return throwable instanceof GoogleAPIConnectionSuspendedException; - } - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.kt new file mode 100644 index 00000000..fb8d9649 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.kt @@ -0,0 +1,35 @@ +package pl.charmas.android.reactivelocation2.observables + +import io.reactivex.Observable +import io.reactivex.ObservableOnSubscribe +import io.reactivex.ObservableSource +import io.reactivex.ObservableTransformer +import io.reactivex.functions.BiPredicate +import pl.charmas.android.reactivelocation2.observables.exceptions.GoogleAPIConnectionSuspendedException + +class ObservableFactory constructor(private val context: ObservableContext) { + fun create(source: ObservableOnSubscribe): Observable { + return Observable.create(source).compose( + RetryOnConnectionSuspension( + context.isRetryOnConnectionSuspended + ) + ) + } + + private class RetryOnConnectionSuspension internal constructor(private val shouldRetry: Boolean) : + ObservableTransformer { + override fun apply(upstream: Observable): ObservableSource { + return if (shouldRetry) { + upstream.retry(IsConnectionSuspendedException()) + } else upstream + } + + private class IsConnectionSuspendedException : + BiPredicate { + @Throws(Exception::class) + override fun test(integer: Int, throwable: Throwable): Boolean { + return throwable is GoogleAPIConnectionSuspendedException + } + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.java deleted file mode 100644 index f70eacad..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.java +++ /dev/null @@ -1,44 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import android.support.annotation.NonNull; - -import com.google.android.gms.common.api.PendingResult; -import com.google.android.gms.common.api.Result; -import com.google.android.gms.common.api.ResultCallback; - -import io.reactivex.ObservableEmitter; -import io.reactivex.ObservableOnSubscribe; -import io.reactivex.disposables.Disposables; -import io.reactivex.functions.Action; - -public class PendingResultObservableOnSubscribe implements ObservableOnSubscribe { - private final PendingResult result; - private boolean complete = false; - - public PendingResultObservableOnSubscribe(PendingResult result) { - this.result = result; - } - - @Override - public void subscribe(final ObservableEmitter emitter) throws Exception { - result.setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull T t) { - if (!emitter.isDisposed()) { - emitter.onNext(t); - emitter.onComplete(); - } - complete = true; - } - }); - - emitter.setDisposable(Disposables.fromAction(new Action() { - @Override - public void run() { - if (!complete) { - result.cancel(); - } - } - })); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.kt new file mode 100644 index 00000000..963096cc --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.kt @@ -0,0 +1,29 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.common.api.PendingResult +import com.google.android.gms.common.api.Result +import io.reactivex.ObservableEmitter +import io.reactivex.ObservableOnSubscribe +import io.reactivex.disposables.Disposables + +class PendingResultObservableOnSubscribe( + private val pendingResult: PendingResult +) : ObservableOnSubscribe { + + private var complete = false + + override fun subscribe(emitter: ObservableEmitter) { + pendingResult.setResultCallback { result: T -> + if (!emitter.isDisposed) { + emitter.onNext(result) + emitter.onComplete() + } + complete = true + } + emitter.setDisposable(Disposables.fromAction { + if (!complete) { + pendingResult.cancel() + } + }) + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.java deleted file mode 100644 index 9a0ff78c..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.java +++ /dev/null @@ -1,16 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables; - -import com.google.android.gms.common.api.Status; - -public class StatusException extends Throwable { - private final Status status; - - public StatusException(Status status) { - super(status.getStatusCode() + ": " + status.getStatusMessage()); - this.status = status; - } - - public Status getStatus() { - return status; - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.kt new file mode 100644 index 00000000..5d2b3c6f --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.kt @@ -0,0 +1,6 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.common.api.Status + +class StatusException(val status: Status) : + Throwable(status.statusCode.toString() + ": " + status.statusMessage) \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt new file mode 100644 index 00000000..d7f88bf1 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/TaskResultMaybeOnSubscribe.kt @@ -0,0 +1,21 @@ +package pl.charmas.android.reactivelocation2.observables + +import com.google.android.gms.tasks.Task +import io.reactivex.MaybeEmitter +import io.reactivex.MaybeOnSubscribe + +class TaskResultMaybeOnSubscribe constructor(private val task: Task) : + MaybeOnSubscribe { + override fun subscribe(emitter: MaybeEmitter) { + task.addOnSuccessListener { result: T? -> + if (!emitter.isDisposed) { + result?.let { emitter.onSuccess(it) } ?: emitter.onComplete() + } + } + task.addOnFailureListener { exception -> + if (!emitter.isDisposed) { + emitter.onError(exception) + } + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/ActivityUpdatesObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/ActivityUpdatesObservableOnSubscribe.java index 60abc354..10c32c70 100644 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/ActivityUpdatesObservableOnSubscribe.java +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/ActivityUpdatesObservableOnSubscribe.java @@ -16,7 +16,12 @@ import pl.charmas.android.reactivelocation2.observables.ObservableFactory; + +/** + * deprecated: need to update to new api of google com.google.android.gms.location.ActivityRecognition#getClient(android.app.Activity) + * */ @SuppressWarnings("MissingPermission") +@Deprecated public class ActivityUpdatesObservableOnSubscribe extends BaseActivityObservableOnSubscribe { private static final String ACTION_ACTIVITY_DETECTED = "pl.charmas.android.reactivelocation2.ACTION_ACTIVITY_UPDATE_DETECTED"; @@ -25,7 +30,7 @@ public class ActivityUpdatesObservableOnSubscribe extends BaseActivityObservable private ActivityUpdatesBroadcastReceiver receiver; public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, int detectionIntervalMiliseconds) { - return factory.createObservable(new ActivityUpdatesObservableOnSubscribe(ctx, detectionIntervalMiliseconds)); + return factory.create(new ActivityUpdatesObservableOnSubscribe(ctx, detectionIntervalMiliseconds)); } private ActivityUpdatesObservableOnSubscribe(ObservableContext context, int detectionIntervalMilliseconds) { diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.java deleted file mode 100644 index 3101ffbd..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.java +++ /dev/null @@ -1,12 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.activity; - -import com.google.android.gms.location.ActivityRecognition; - -import pl.charmas.android.reactivelocation2.observables.BaseObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; - -abstract class BaseActivityObservableOnSubscribe extends BaseObservableOnSubscribe { - BaseActivityObservableOnSubscribe(ObservableContext ctx) { - super(ctx, ActivityRecognition.API); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.kt new file mode 100644 index 00000000..bb045002 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.kt @@ -0,0 +1,8 @@ +package pl.charmas.android.reactivelocation2.observables.activity + +import com.google.android.gms.location.ActivityRecognition +import pl.charmas.android.reactivelocation2.observables.BaseObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.ObservableContext + +internal abstract class BaseActivityObservableOnSubscribe constructor(ctx: ObservableContext) : + BaseObservableOnSubscribe(ctx, ActivityRecognition.API) \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionException.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionException.kt new file mode 100644 index 00000000..26f43287 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionException.kt @@ -0,0 +1,8 @@ +package pl.charmas.android.reactivelocation2.observables.exceptions + +import com.google.android.gms.common.ConnectionResult + +class GoogleAPIConnectionException internal constructor( + detailMessage: String?, + val connectionResult: ConnectionResult +) : RuntimeException(detailMessage) \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionSuspendedException.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionSuspendedException.kt new file mode 100644 index 00000000..51fe6b31 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/exceptions/GoogleAPIConnectionSuspendedException.kt @@ -0,0 +1,4 @@ +package pl.charmas.android.reactivelocation2.observables.exceptions + +class GoogleAPIConnectionSuspendedException internal constructor(val errorCause: Int) : + RuntimeException() \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.java deleted file mode 100644 index 90ddd45e..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.java +++ /dev/null @@ -1,157 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geocode; - -import android.location.Address; -import android.text.TextUtils; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -import io.reactivex.ObservableEmitter; -import io.reactivex.ObservableOnSubscribe; - -class FallbackReverseGeocodeObservable implements ObservableOnSubscribe> { - private final Locale locale; - private final double latitude; - private final double longitude; - private final int maxResults; - - FallbackReverseGeocodeObservable(Locale locale, double latitude, double longitude, int maxResults) { - this.locale = locale; - this.latitude = latitude; - this.longitude = longitude; - this.maxResults = maxResults; - } - - @Override - public void subscribe(ObservableEmitter> emitter) throws Exception { - try { - List

addresses = alternativeReverseGeocodeQuery(); - if (!emitter.isDisposed()) { - emitter.onNext(addresses); - emitter.onComplete(); - } - } catch (Exception ex) { - if (!emitter.isDisposed()) { - emitter.onError(ex); - } - } - } - - /** - * This function fetches a list of addresses for the set latitude, longitude and maxResults properties from the - * Google Geocode API (http://maps.googleapis.com/maps/api/geocode). - * - * @return List of addresses - * @throws IOException In case of network problems - * @throws JSONException In case of problems while parsing the json response from google geocode API servers - */ - private List
alternativeReverseGeocodeQuery() throws IOException, JSONException { - URL url = new URL(String.format(Locale.ENGLISH, - "http://maps.googleapis.com/maps/api/geocode/json?" - + "latlng=%1$f,%2$f&sensor=true&language=%3$s", - latitude, longitude, locale.getLanguage() - )); - HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); - StringBuilder stringBuilder = new StringBuilder(); - List
outResult = new ArrayList<>(); - - try { - BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8")); - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line); - } - - // Root json response object - JSONObject jsonRootObject = new JSONObject(stringBuilder.toString()); - - // No results status - if ("ZERO_RESULTS".equalsIgnoreCase(jsonRootObject.getString("status"))) { - return Collections.emptyList(); - } - - // Other non-OK responses status - if (!"OK".equalsIgnoreCase(jsonRootObject.getString("status"))) { - throw new RuntimeException("Wrong API response"); - } - - // Process results - JSONArray results = jsonRootObject.getJSONArray("results"); - for (int i = 0; i < results.length() && i < maxResults; i++) { - Address address = new Address(Locale.getDefault()); - String addressLineString = ""; - JSONObject sourceResult = results.getJSONObject(i); - JSONArray addressComponents = sourceResult.getJSONArray("address_components"); - - // Assemble address by various components - for (int ac = 0; ac < addressComponents.length(); ac++) { - String longNameVal = addressComponents.getJSONObject(ac).getString("long_name"); - String shortNameVal = addressComponents.getJSONObject(ac).getString("short_name"); - JSONArray acTypes = addressComponents.getJSONObject(ac).getJSONArray("types"); - String acType = acTypes.getString(0); - - if (!TextUtils.isEmpty(longNameVal)) { - if (acType.equalsIgnoreCase("street_number")) { - if (TextUtils.isEmpty(addressLineString)) { - addressLineString = longNameVal; - } else { - addressLineString += " " + longNameVal; - } - } else if (acType.equalsIgnoreCase("route")) { - if (TextUtils.isEmpty(addressLineString)) { - addressLineString = longNameVal; - } else { - addressLineString = longNameVal + " " + addressLineString; - } - } else if (acType.equalsIgnoreCase("sublocality")) { - address.setSubLocality(longNameVal); - } else if (acType.equalsIgnoreCase("locality")) { - address.setLocality(longNameVal); - } else if (acType.equalsIgnoreCase("administrative_area_level_2")) { - address.setSubAdminArea(longNameVal); - } else if (acType.equalsIgnoreCase("administrative_area_level_1")) { - address.setAdminArea(longNameVal); - } else if (acType.equalsIgnoreCase("country")) { - address.setCountryName(longNameVal); - address.setCountryCode(shortNameVal); - } else if (acType.equalsIgnoreCase("postal_code")) { - address.setPostalCode(longNameVal); - } - } - } - - // Try to get the already formatted address - String formattedAddress = sourceResult.getString("formatted_address"); - if (!TextUtils.isEmpty(formattedAddress)) { - String[] formattedAddressLines = formattedAddress.split(","); - - for (int ia = 0; ia < formattedAddressLines.length; ia++) { - address.setAddressLine(ia, formattedAddressLines[ia].trim()); - } - } else if (!TextUtils.isEmpty(addressLineString)) { - // If that fails use our manually assembled formatted address - address.setAddressLine(0, addressLineString); - } - - // Finally add address to resulting set - outResult.add(address); - } - - } finally { - urlConnection.disconnect(); - } - - return Collections.unmodifiableList(outResult); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt new file mode 100644 index 00000000..a5444fa0 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.kt @@ -0,0 +1,154 @@ +package pl.charmas.android.reactivelocation2.observables.geocode + +import android.location.Address +import io.reactivex.MaybeEmitter +import io.reactivex.MaybeOnSubscribe +import org.json.JSONException +import org.json.JSONObject +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStreamReader +import java.net.HttpURLConnection +import java.net.URL +import java.util.Collections +import java.util.Locale + +internal class FallbackReverseGeocodeObservable( + private val apiKey: String, + private val locale: Locale, + private val latitude: Double, + private val longitude: Double, + private val maxResults: Int +) : MaybeOnSubscribe> { + + override fun subscribe(emitter: MaybeEmitter?>) { + try { + val addresses = alternativeReverseGeocodeQuery() + if (!emitter.isDisposed) { + emitter.onSuccess(addresses) + } + } catch (exception: Exception) { + if (!emitter.isDisposed) { + emitter.onError(exception) + } + } + } + + /** + * This function fetches a list of addresses for the set latitude, longitude and maxResults properties from the + * Google Geocode API (https://maps.googleapis.com/maps/api/geocode). + * + * @return List of addresses + * @throws IOException In case of network problems + * @throws JSONException In case of problems while parsing the json response from google geocode API servers + */ + @Throws(IOException::class, JSONException::class) + private fun alternativeReverseGeocodeQuery(): List
{ + val url = URL( + "https://maps.googleapis.com/maps/api/geocode/json?" + + "latlng=$latitude,${longitude}&sensor=true&key=$apiKey&language=${locale.language}" + ) + val urlConnection = url.openConnection() as HttpURLConnection + val stringBuilder = StringBuilder() + val outResult = mutableListOf
() + try { + val reader = + BufferedReader(InputStreamReader(urlConnection.inputStream, Charsets.UTF_8)) + var line: String? + while (reader.readLine().also { line = it } != null) { + stringBuilder.append(line) + } + + // Root json response object + val jsonRootObject = JSONObject(stringBuilder.toString()) + + // No results status + if ("ZERO_RESULTS".equals(jsonRootObject.getString("status"), ignoreCase = true)) { + return emptyList() + } + + // Other non-OK responses status + if (!"OK".equals(jsonRootObject.getString("status"), ignoreCase = true)) { + throw RuntimeException("Wrong API response") + } + + // Process results + val results = jsonRootObject.getJSONArray("results") + var i = 0 + while (i < results.length() && i < maxResults) { + val address = Address(Locale.getDefault()) + var addressLineString = "" + val sourceResult = results.getJSONObject(i) + val addressComponents = sourceResult.getJSONArray("address_components") + + // Assemble address by various components + for (ac in 0 until addressComponents.length()) { + val jsonObject = addressComponents.getJSONObject(ac) + val longNameVal = jsonObject.getString("long_name") + val shortNameVal = jsonObject.getString("short_name") + val acTypes = jsonObject.getJSONArray("types") + val acType = acTypes.getString(0) + if (longNameVal.isNotEmpty()) { + if (acType.equals("street_number", ignoreCase = true)) { + if (addressLineString.isEmpty()) { + addressLineString = longNameVal + } else { + addressLineString += " $longNameVal" + } + } else if (acType.equals("route", ignoreCase = true)) { + addressLineString = if (addressLineString.isEmpty()) { + longNameVal + } else { + "$longNameVal $addressLineString" + } + } else if (acType.equals("sublocality", ignoreCase = true)) { + address.subLocality = longNameVal + } else if (acType.equals("locality", ignoreCase = true)) { + address.locality = longNameVal + } else if (acType.equals( + "administrative_area_level_2", + ignoreCase = true + ) + ) { + address.subAdminArea = longNameVal + } else if (acType.equals( + "administrative_area_level_1", + ignoreCase = true + ) + ) { + address.adminArea = longNameVal + } else if (acType.equals("country", ignoreCase = true)) { + address.countryName = longNameVal + address.countryCode = shortNameVal + } else if (acType.equals("postal_code", ignoreCase = true)) { + address.postalCode = longNameVal + } + } + } + + // Try to get the already formatted address + val formattedAddress = sourceResult.getString("formatted_address") + if (formattedAddress.isNotEmpty()) { + val formattedAddressLines = + formattedAddress.split(",".toRegex()).toTypedArray() + for (ia in formattedAddressLines.indices) { + address.setAddressLine( + ia, + formattedAddressLines[ia].trim { it <= ' ' } + ) + } + } else if (addressLineString.isNotEmpty()) { + // If that fails use our manually assembled formatted address + address.setAddressLine(0, addressLineString) + } + + // Finally add address to resulting set + outResult.add(address) + i++ + } + } finally { + urlConnection.disconnect() + } + return Collections.unmodifiableList(outResult) + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeMaybe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeMaybe.kt new file mode 100644 index 00000000..f5e68eff --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeMaybe.kt @@ -0,0 +1,73 @@ +package pl.charmas.android.reactivelocation2.observables.geocode + +import android.content.Context +import android.location.Address +import android.location.Geocoder +import com.google.android.gms.maps.model.LatLngBounds +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import io.reactivex.MaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.MaybeFactory +import pl.charmas.android.reactivelocation2.observables.ObservableFactory +import java.io.IOException +import java.util.Locale + +class GeocodeMaybe private constructor( + private val ctx: Context, + private val locationName: String, + private val maxResults: Int, + private val bounds: LatLngBounds? = null, + private val locale: Locale +) : MaybeOnSubscribe> { + + @Throws(Exception::class) + override fun subscribe(emitter: MaybeEmitter>) { + val geoCoder = createGeocoder() + try { + val result = getAddresses(geoCoder) + if (!emitter.isDisposed) { + emitter.onSuccess(result) + } + } catch (e: IOException) { + if (!emitter.isDisposed) { + emitter.onError(e) + } + } + } + + @Throws(IOException::class) + private fun getAddresses(geocoder: Geocoder): List
{ + return if (bounds != null) { + geocoder.getFromLocationName( + locationName, + maxResults, + bounds.southwest.latitude, + bounds.southwest.longitude, + bounds.northeast.latitude, + bounds.northeast.longitude + ) + } else { + geocoder.getFromLocationName(locationName, maxResults) + } + } + + private fun createGeocoder(): Geocoder { + return Geocoder(ctx, locale) + } + + companion object { + fun create( + ctx: Context, + factory: MaybeFactory, + locationName: String, + maxResults: Int, + bounds: LatLngBounds? = null, + locale: Locale + ): Maybe> { + return factory.create( + GeocodeMaybe(ctx, locationName, maxResults, bounds, locale) + ) + } + } + +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeObservable.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeObservable.java deleted file mode 100644 index e74f1fc8..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeObservable.java +++ /dev/null @@ -1,69 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geocode; - -import android.content.Context; -import android.location.Address; -import android.location.Geocoder; -import android.support.annotation.NonNull; - -import com.google.android.gms.maps.model.LatLngBounds; - -import java.io.IOException; -import java.util.List; -import java.util.Locale; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import io.reactivex.ObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; - -public class GeocodeObservable implements ObservableOnSubscribe> { - private final Context ctx; - private final String locationName; - private final int maxResults; - private final LatLngBounds bounds; - private final Locale locale; - - public static Observable> createObservable(Context ctx, ObservableFactory factory, String locationName, int maxResults, LatLngBounds bounds, Locale locale) { - return factory.createObservable(new GeocodeObservable(ctx, locationName, maxResults, bounds, locale)); - } - - private GeocodeObservable(Context ctx, String locationName, int maxResults, LatLngBounds bounds, Locale locale) { - this.ctx = ctx; - this.locationName = locationName; - this.maxResults = maxResults; - this.bounds = bounds; - this.locale = locale; - } - - @Override - public void subscribe(ObservableEmitter> emitter) throws Exception { - Geocoder geocoder = createGeocoder(); - try { - List
result = getAddresses(geocoder); - if (!emitter.isDisposed()) { - emitter.onNext(result); - emitter.onComplete(); - } - } catch (IOException e) { - if (!emitter.isDisposed()) { - emitter.onError(e); - } - } - } - - private List
getAddresses(Geocoder geocoder) throws IOException { - List
result; - if (bounds != null) { - result = geocoder.getFromLocationName(locationName, maxResults, bounds.southwest.latitude, bounds.southwest.longitude, bounds.northeast.latitude, bounds.northeast.longitude); - } else { - result = geocoder.getFromLocationName(locationName, maxResults); - } - return result; - } - - @NonNull - private Geocoder createGeocoder() { - if (locale != null) return new Geocoder(ctx, locale); - return new Geocoder(ctx); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.java deleted file mode 100644 index bbcccf5f..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.java +++ /dev/null @@ -1,57 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geocode; - -import android.content.Context; -import android.location.Address; -import android.location.Geocoder; - -import java.io.IOException; -import java.util.List; -import java.util.Locale; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import io.reactivex.ObservableOnSubscribe; -import io.reactivex.schedulers.Schedulers; -import pl.charmas.android.reactivelocation2.observables.ObservableEmitterWrapper; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; - - -public class ReverseGeocodeObservable implements ObservableOnSubscribe> { - private final Context ctx; - private final Locale locale; - private final double latitude; - private final double longitude; - private final int maxResults; - - public static Observable> createObservable(Context ctx, ObservableFactory factory, Locale locale, double latitude, double longitude, int maxResults) { - return factory.createObservable(new ReverseGeocodeObservable(ctx, locale, latitude, longitude, maxResults)); - } - - private ReverseGeocodeObservable(Context ctx, Locale locale, double latitude, double longitude, int maxResults) { - this.ctx = ctx; - this.latitude = latitude; - this.longitude = longitude; - this.maxResults = maxResults; - this.locale = locale; - } - - @Override - public void subscribe(ObservableEmitter> emitter) throws Exception { - Geocoder geocoder = new Geocoder(ctx, locale); - try { - List
addresses = geocoder.getFromLocation(latitude, longitude, maxResults); - if (!emitter.isDisposed()) { - emitter.onNext(addresses); - emitter.onComplete(); - } - } catch (IOException e) { - // If it's a service not available error try a different approach using google web api - if (!emitter.isDisposed()) { - Observable - .create(new FallbackReverseGeocodeObservable(locale, latitude, longitude, maxResults)) - .subscribeOn(Schedulers.io()) - .subscribe(new ObservableEmitterWrapper<>(emitter)); - } - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.kt new file mode 100644 index 00000000..c19b91d9 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.kt @@ -0,0 +1,64 @@ +package pl.charmas.android.reactivelocation2.observables.geocode + +import android.content.Context +import android.location.Address +import android.location.Geocoder +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import io.reactivex.MaybeOnSubscribe +import io.reactivex.schedulers.Schedulers +import pl.charmas.android.reactivelocation2.observables.MaybeEmitterWrapper +import pl.charmas.android.reactivelocation2.observables.MaybeFactory +import java.io.IOException +import java.util.Locale + +class ReverseGeocodeObservable private constructor( + private val ctx: Context, + private val locale: Locale, + private val apiKey: String, + private val latitude: Double, + private val longitude: Double, + private val maxResults: Int +) : MaybeOnSubscribe> { + + override fun subscribe(emitter: MaybeEmitter>) { + val geoCoder = Geocoder(ctx, locale) + try { + val addresses = geoCoder.getFromLocation(latitude, longitude, maxResults) + if (!emitter.isDisposed) { + emitter.onSuccess(addresses) + } + } catch (e: IOException) { + // If it's a service not available error try a different approach using google web api + if (!emitter.isDisposed) { + Maybe.create( + FallbackReverseGeocodeObservable( + apiKey, + locale, + latitude, + longitude, + maxResults + ) + ) + .subscribeOn(Schedulers.io()) + .subscribe(MaybeEmitterWrapper(emitter)) + } + } + } + + companion object { + fun create( + ctx: Context, + apiKey: String, + factory: MaybeFactory, + locale: Locale, + latitude: Double, + longitude: Double, + maxResults: Int + ): Maybe> { + return factory.create( + ReverseGeocodeObservable(ctx, locale, apiKey, latitude, longitude, maxResults) + ) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceMaybeOnSubscribe.kt new file mode 100644 index 00000000..f086f299 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceMaybeOnSubscribe.kt @@ -0,0 +1,55 @@ +package pl.charmas.android.reactivelocation2.observables.geofence + +import android.app.PendingIntent +import androidx.annotation.RequiresPermission +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.GeofencingClient +import com.google.android.gms.location.GeofencingRequest +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import pl.charmas.android.reactivelocation2.observables.BaseLocationMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.MaybeContext +import pl.charmas.android.reactivelocation2.observables.MaybeFactory + +class AddGeofenceMaybeOnSubscribe private constructor( + ctx: MaybeContext, + private val request: GeofencingRequest, + private val geofenceTransitionPendingIntent: PendingIntent, + private val geofencingClient: GeofencingClient +) : BaseLocationMaybeOnSubscribe(ctx) { + + @RequiresPermission("android.permission.ACCESS_FINE_LOCATION") + override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + geofencingClient.addGeofences(request, geofenceTransitionPendingIntent) + .addOnSuccessListener { sd: Void? -> + if (emitter.isDisposed) return@addOnSuccessListener + emitter.onSuccess(true) + } + .addOnFailureListener { error: Exception? -> + if (emitter.isDisposed) return@addOnFailureListener + emitter.onError(error!!) + } + } + + companion object { + fun createMaybe( + geofencingClient: GeofencingClient, + ctx: MaybeContext, + factory: MaybeFactory, + request: GeofencingRequest, + geofenceTransitionPendingIntent: PendingIntent + ): Maybe { + return factory.create( + AddGeofenceMaybeOnSubscribe( + ctx, + request, + geofenceTransitionPendingIntent, + geofencingClient + ) + ) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceObservableOnSubscribe.java deleted file mode 100644 index 300973d9..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceObservableOnSubscribe.java +++ /dev/null @@ -1,53 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geofence; - -import android.app.PendingIntent; -import android.support.annotation.NonNull; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.GeofencingRequest; -import com.google.android.gms.location.LocationServices; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; -import pl.charmas.android.reactivelocation2.observables.StatusException; - - -@SuppressWarnings("MissingPermission") -public class AddGeofenceObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - private final GeofencingRequest request; - private final PendingIntent geofenceTransitionPendingIntent; - - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, GeofencingRequest request, PendingIntent geofenceTransitionPendingIntent) { - return factory.createObservable(new AddGeofenceObservableOnSubscribe(ctx, request, geofenceTransitionPendingIntent)); - } - - private AddGeofenceObservableOnSubscribe(ObservableContext ctx, GeofencingRequest request, PendingIntent geofenceTransitionPendingIntent) { - super(ctx); - this.request = request; - this.geofenceTransitionPendingIntent = geofenceTransitionPendingIntent; - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { - LocationServices.GeofencingApi.addGeofences(apiClient, request, geofenceTransitionPendingIntent) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (emitter.isDisposed()) return; - if (status.isSuccess()) { - emitter.onNext(status); - emitter.onComplete(); - - } else { - emitter.onError(new StatusException(status)); - } - } - }); - } - -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentMaybeOnSubscribe.kt new file mode 100644 index 00000000..1b8b591a --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentMaybeOnSubscribe.kt @@ -0,0 +1,29 @@ +package pl.charmas.android.reactivelocation2.observables.geofence + +import android.app.PendingIntent +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.GeofencingClient +import io.reactivex.MaybeEmitter +import pl.charmas.android.reactivelocation2.observables.MaybeContext + +class RemoveGeofenceByPendingIntentMaybeOnSubscribe( + ctx: MaybeContext, + private val pendingIntent: PendingIntent, + private val geofencingClient: GeofencingClient +) : RemoveGeofenceMaybeOnSubscribe(ctx) { + + override fun removeGeofences( + locationClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + geofencingClient.removeGeofences(pendingIntent) + .addOnSuccessListener { + if (emitter.isDisposed) return@addOnSuccessListener + emitter.onSuccess(true) + } + .addOnFailureListener { error -> + if (emitter.isDisposed) return@addOnFailureListener + emitter.onError(error) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentObservableOnSubscribe.java deleted file mode 100644 index 551d9b70..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentObservableOnSubscribe.java +++ /dev/null @@ -1,40 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geofence; - -import android.app.PendingIntent; -import android.support.annotation.NonNull; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.LocationServices; - -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.StatusException; - - -class RemoveGeofenceByPendingIntentObservableOnSubscribe extends RemoveGeofenceObservableOnSubscribe { - private final PendingIntent pendingIntent; - - RemoveGeofenceByPendingIntentObservableOnSubscribe(ObservableContext ctx, PendingIntent pendingIntent) { - super(ctx); - this.pendingIntent = pendingIntent; - } - - @Override - protected void removeGeofences(GoogleApiClient locationClient, final ObservableEmitter emitter) { - LocationServices.GeofencingApi.removeGeofences(locationClient, pendingIntent) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (emitter.isDisposed()) return; - if (status.isSuccess()) { - emitter.onNext(status); - emitter.onComplete(); - } else { - emitter.onError(new StatusException(status)); - } - } - }); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceMaybeOnSubscribe.kt new file mode 100644 index 00000000..0f01dbdd --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceMaybeOnSubscribe.kt @@ -0,0 +1,58 @@ +package pl.charmas.android.reactivelocation2.observables.geofence + +import android.app.PendingIntent +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.GeofencingClient +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import pl.charmas.android.reactivelocation2.observables.BaseLocationMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.MaybeContext +import pl.charmas.android.reactivelocation2.observables.MaybeFactory + +abstract class RemoveGeofenceMaybeOnSubscribe internal constructor(ctx: MaybeContext) : + BaseLocationMaybeOnSubscribe(ctx) { + + override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + removeGeofences(apiClient, emitter) + } + + protected abstract fun removeGeofences( + locationClient: GoogleApiClient, + emitter: MaybeEmitter + ) + + companion object { + fun createMaybe( + ctx: MaybeContext, + factory: MaybeFactory, + pendingIntent: PendingIntent, + geofencingClient: GeofencingClient + ): Maybe { + return factory.create( + RemoveGeofenceByPendingIntentMaybeOnSubscribe( + ctx, + pendingIntent, + geofencingClient + ) + ) + } + + fun createMaybe( + ctx: MaybeContext, + factory: MaybeFactory, + requestIds: List, + geofencingClient: GeofencingClient + ): Maybe { + return factory.create( + RemoveGeofenceRequestIdsMaybeOnSubscribe( + ctx, + requestIds, + geofencingClient + ) + ) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceObservableOnSubscribe.java deleted file mode 100644 index a5c4db0d..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceObservableOnSubscribe.java +++ /dev/null @@ -1,38 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geofence; - -import android.app.PendingIntent; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.Status; - -import java.util.List; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; - - -public abstract class RemoveGeofenceObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, PendingIntent pendingIntent) { - return factory.createObservable(new RemoveGeofenceByPendingIntentObservableOnSubscribe(ctx, pendingIntent)); - } - - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, List requestIds) { - return factory.createObservable(new RemoveGeofenceRequestIdsObservableOnSubscribe(ctx, requestIds)); - } - - RemoveGeofenceObservableOnSubscribe(ObservableContext ctx) { - super(ctx); - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { - removeGeofences(apiClient, emitter); - } - - protected abstract void removeGeofences(GoogleApiClient locationClient, ObservableEmitter emitter); - -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsMaybeOnSubscribe.kt new file mode 100644 index 00000000..60d0f2a8 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsMaybeOnSubscribe.kt @@ -0,0 +1,30 @@ +package pl.charmas.android.reactivelocation2.observables.geofence + +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.GeofencingClient +import io.reactivex.MaybeEmitter +import pl.charmas.android.reactivelocation2.observables.MaybeContext + +internal class RemoveGeofenceRequestIdsMaybeOnSubscribe constructor( + ctx: MaybeContext, + private val geofenceRequestIds: List, + private val geofencingClient: GeofencingClient +) : RemoveGeofenceMaybeOnSubscribe(ctx) { + + override fun removeGeofences( + locationClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + geofencingClient.removeGeofences(geofenceRequestIds) + .addOnSuccessListener { + if (!emitter.isDisposed) { + emitter.onSuccess(true) + } + } + .addOnFailureListener { error -> + if (!emitter.isDisposed) { + emitter.onError(error) + } + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsObservableOnSubscribe.java deleted file mode 100644 index 557b86e9..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsObservableOnSubscribe.java +++ /dev/null @@ -1,41 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.geofence; - -import android.support.annotation.NonNull; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.LocationServices; - -import java.util.List; - -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.StatusException; - - -class RemoveGeofenceRequestIdsObservableOnSubscribe extends RemoveGeofenceObservableOnSubscribe { - private final List geofenceRequestIds; - - RemoveGeofenceRequestIdsObservableOnSubscribe(ObservableContext ctx, List geofenceRequestIds) { - super(ctx); - this.geofenceRequestIds = geofenceRequestIds; - } - - @Override - protected void removeGeofences(GoogleApiClient locationClient, final ObservableEmitter emitter) { - LocationServices.GeofencingApi.removeGeofences(locationClient, geofenceRequestIds) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (emitter.isDisposed()) return; - if (status.isSuccess()) { - emitter.onNext(status); - emitter.onComplete(); - } else { - emitter.onError(new StatusException(status)); - } - } - }); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesMaybeOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesMaybeOnSubscribe.kt new file mode 100644 index 00000000..08bde7c5 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesMaybeOnSubscribe.kt @@ -0,0 +1,57 @@ +package pl.charmas.android.reactivelocation2.observables.location + +import android.app.PendingIntent +import androidx.annotation.RequiresPermission +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationRequest +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import pl.charmas.android.reactivelocation2.observables.BaseLocationMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.MaybeContext +import pl.charmas.android.reactivelocation2.observables.MaybeFactory + +class AddLocationIntentUpdatesMaybeOnSubscribe private constructor( + ctx: MaybeContext, + private val fusedLocationProviderClient: FusedLocationProviderClient, + private val locationRequest: LocationRequest, + private val intent: PendingIntent +) : BaseLocationMaybeOnSubscribe(ctx) { + + @RequiresPermission("android.permission.ACCESS_FINE_LOCATION") + override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + fusedLocationProviderClient.requestLocationUpdates(locationRequest, intent) + .addOnSuccessListener { + if (!emitter.isDisposed) { + emitter.onSuccess(true) + } + } + .addOnFailureListener { error -> + if (!emitter.isDisposed) { + emitter.onError(error) + } + } + } + + companion object { + fun create( + fusedLocationProviderClient: FusedLocationProviderClient, + ctx: MaybeContext, + factory: MaybeFactory, + locationRequest: LocationRequest, + intent: PendingIntent + ): Maybe { + return factory.create( + AddLocationIntentUpdatesMaybeOnSubscribe( + ctx, + fusedLocationProviderClient, + locationRequest, + intent + ) + ) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesObservableOnSubscribe.java deleted file mode 100644 index 5774a55f..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesObservableOnSubscribe.java +++ /dev/null @@ -1,52 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.location; - -import android.app.PendingIntent; -import android.support.annotation.NonNull; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.LocationRequest; -import com.google.android.gms.location.LocationServices; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; -import pl.charmas.android.reactivelocation2.observables.StatusException; - - -@SuppressWarnings("MissingPermission") -public class AddLocationIntentUpdatesObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - private final LocationRequest locationRequest; - private final PendingIntent intent; - - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, LocationRequest locationRequest, PendingIntent intent) { - return factory.createObservable(new AddLocationIntentUpdatesObservableOnSubscribe(ctx, locationRequest, intent)); - } - - private AddLocationIntentUpdatesObservableOnSubscribe(ObservableContext ctx, LocationRequest locationRequest, PendingIntent intent) { - super(ctx); - this.locationRequest = locationRequest; - this.intent = intent; - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { - LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, locationRequest, intent) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (emitter.isDisposed()) return; - if (!status.isSuccess()) { - emitter.onError(new StatusException(status)); - } else { - emitter.onNext(status); - emitter.onComplete(); - } - } - }); - - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LastKnownLocationObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LastKnownLocationObservableOnSubscribe.java deleted file mode 100644 index 3a6fef6a..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LastKnownLocationObservableOnSubscribe.java +++ /dev/null @@ -1,33 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.location; - -import android.location.Location; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.location.LocationServices; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; - -@SuppressWarnings("MissingPermission") -public class LastKnownLocationObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory) { - return factory.createObservable(new LastKnownLocationObservableOnSubscribe(ctx)); - } - - private LastKnownLocationObservableOnSubscribe(ObservableContext ctx) { - super(ctx); - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, ObservableEmitter emitter) { - Location location = LocationServices.FusedLocationApi.getLastLocation(apiClient); - if (emitter.isDisposed()) return; - if (location != null) { - emitter.onNext(location); - } - emitter.onComplete(); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.java deleted file mode 100644 index 5b0c0254..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.java +++ /dev/null @@ -1,66 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.location; - -import android.location.Location; - -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 java.lang.ref.WeakReference; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; - - -@SuppressWarnings("MissingPermission") -public class LocationUpdatesObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, LocationRequest locationRequest) { - Observable observable = factory.createObservable(new LocationUpdatesObservableOnSubscribe(ctx, locationRequest)); - int requestedNumberOfUpdates = locationRequest.getNumUpdates(); - if (requestedNumberOfUpdates > 0 && requestedNumberOfUpdates < Integer.MAX_VALUE) { - observable = observable.take(requestedNumberOfUpdates); - } - return observable; - } - - private final LocationRequest locationRequest; - private LocationListener listener; - - private LocationUpdatesObservableOnSubscribe(ObservableContext ctx, LocationRequest locationRequest) { - super(ctx); - this.locationRequest = locationRequest; - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { - listener = new LocationUpdatesLocationListener(emitter); - LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, locationRequest, listener); - } - - @Override - protected void onDisposed(GoogleApiClient locationClient) { - if (locationClient.isConnected()) { - LocationServices.FusedLocationApi.removeLocationUpdates(locationClient, listener); - } - } - - private static class LocationUpdatesLocationListener implements LocationListener { - private final WeakReference> weakRef; - - LocationUpdatesLocationListener(ObservableEmitter emitter) { - this.weakRef = new WeakReference>(emitter); - } - - @Override - public void onLocationChanged(Location location) { - final ObservableEmitter observer = weakRef.get(); - if (observer != null && !observer.isDisposed()) { - observer.onNext(location); - } - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.kt new file mode 100644 index 00000000..cf26ec4a --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.kt @@ -0,0 +1,85 @@ +package pl.charmas.android.reactivelocation2.observables.location + +import android.annotation.SuppressLint +import android.location.Location +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationCallback +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationResult +import io.reactivex.Observable +import io.reactivex.ObservableEmitter +import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.ObservableContext +import pl.charmas.android.reactivelocation2.observables.ObservableFactory +import java.lang.ref.WeakReference + +class LocationUpdatesObservableOnSubscribe private constructor( + private val fusedLocationProviderClient: FusedLocationProviderClient, + ctx: ObservableContext, + private val locationRequest: LocationRequest +) : BaseLocationObservableOnSubscribe(ctx) { + private var listener: LocationCallback? = null + + @SuppressLint("MissingPermission") + override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: ObservableEmitter + ) { + listener = LocationUpdatesLocationListener(emitter) + fusedLocationProviderClient + .requestLocationUpdates( + locationRequest, + listener, + null + ) + } + + override fun onDisposed(locationClient: GoogleApiClient) { + if (locationClient.isConnected && listener != null) { + fusedLocationProviderClient.removeLocationUpdates(listener) + } + } + + private class LocationUpdatesLocationListener internal constructor(emitter: ObservableEmitter) : + LocationCallback() { + + private val weakRef: WeakReference> = WeakReference(emitter) + + override fun onLocationResult(locationResult: LocationResult?) { + super.onLocationResult(locationResult) + val observer = weakRef.get() + val locations = locationResult?.locations ?: emptyList() + for (item in locations) { + if (observer != null && !observer.isDisposed && item != null) { + observer.onNext(item) + } + } + + locationResult?.lastLocation?.let {item-> + if (observer != null && !observer.isDisposed) { + observer.onNext(item) + } + } + } + } + + companion object { + fun createObservable( + fusedLocationProviderClient: FusedLocationProviderClient, + ctx: ObservableContext, + factory: ObservableFactory, + locationRequest: LocationRequest + ): Observable { + var observable = + factory.create( + LocationUpdatesObservableOnSubscribe(fusedLocationProviderClient, ctx, locationRequest) + ) + val requestedNumberOfUpdates = locationRequest.numUpdates + if (requestedNumberOfUpdates > 0 && requestedNumberOfUpdates < Int.MAX_VALUE) { + observable = observable.take(requestedNumberOfUpdates.toLong()) + } + return observable + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.java deleted file mode 100644 index f8c6cc76..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.java +++ /dev/null @@ -1,100 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.location; - -import android.location.Location; -import android.support.annotation.NonNull; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.LocationServices; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Action; -import io.reactivex.functions.Consumer; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; -import pl.charmas.android.reactivelocation2.observables.StatusException; - -@SuppressWarnings("MissingPermission") -public class MockLocationObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - private final Observable locationObservable; - private Disposable mockLocationSubscription; - - public static Observable createObservable(ObservableContext context, ObservableFactory factory, Observable locationObservable) { - return factory.createObservable(new MockLocationObservableOnSubscribe(context, locationObservable)); - } - - private MockLocationObservableOnSubscribe(ObservableContext ctx, Observable locationObservable) { - super(ctx); - this.locationObservable = locationObservable; - } - - @Override - protected void onGoogleApiClientReady(final GoogleApiClient apiClient, final ObservableEmitter emitter) { - // this throws SecurityException if permissions are bad or mock locations are not enabled, - // which is passed to observer's onError by BaseObservable - LocationServices.FusedLocationApi.setMockMode(apiClient, true) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (status.isSuccess()) { - startLocationMocking(apiClient, emitter); - } else { - emitter.onError(new StatusException(status)); - } - } - }); - } - - private void startLocationMocking(final GoogleApiClient apiClient, final ObservableEmitter emitter) { - mockLocationSubscription = locationObservable - .subscribe(new Consumer() { - @Override - public void accept(Location location) throws Exception { - LocationServices.FusedLocationApi.setMockLocation(apiClient, location) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (status.isSuccess()) { - emitter.onNext(status); - } else { - emitter.onError(new StatusException(status)); - } - } - }); - } - }, - - new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - emitter.onError(throwable); - } - - }, new Action() { - @Override - public void run() throws Exception { - emitter.onComplete(); - } - }); - - } - - @Override - protected void onDisposed(GoogleApiClient locationClient) { - if (locationClient.isConnected()) { - try { - LocationServices.FusedLocationApi.setMockMode(locationClient, false); - } catch (SecurityException e) { - // if this happens then we couldn't have switched mock mode on in the first place, - // and the observer's onError will already have been called - } - } - if (mockLocationSubscription != null && !mockLocationSubscription.isDisposed()) { - mockLocationSubscription.dispose(); - } - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.kt new file mode 100644 index 00000000..3ccc1566 --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.kt @@ -0,0 +1,97 @@ +package pl.charmas.android.reactivelocation2.observables.location + +import android.annotation.SuppressLint +import android.location.Location +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.FusedLocationProviderClient +import io.reactivex.Observable +import io.reactivex.ObservableEmitter +import io.reactivex.disposables.Disposable +import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe +import pl.charmas.android.reactivelocation2.observables.ObservableContext +import pl.charmas.android.reactivelocation2.observables.ObservableFactory + +class MockLocationObservableOnSubscribe private constructor( + private val fusedLocationProviderClient: FusedLocationProviderClient, + ctx: ObservableContext, + private val locationObservable: Observable, +) : BaseLocationObservableOnSubscribe(ctx) { + private var mockLocationSubscription: Disposable? = null + + @SuppressLint("MissingPermission") + protected override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: ObservableEmitter, + ) { + // this throws SecurityException if permissions are bad or mock locations are not enabled, + // which is passed to observer's onError by BaseObservable + fusedLocationProviderClient.setMockMode(true) + .addOnSuccessListener { + startLocationMocking(emitter) + } + .addOnFailureListener { exception -> + if (!emitter.isDisposed) { + emitter.onError(exception) + } + } + } + + @SuppressLint("MissingPermission") + private fun startLocationMocking(emitter: ObservableEmitter) { + mockLocationSubscription = locationObservable + .subscribe({ location -> + fusedLocationProviderClient.setMockLocation(location) + .addOnSuccessListener { + if (!emitter.isDisposed) { + emitter.onNext(true) + } + } + .addOnFailureListener { error -> + if (!emitter.isDisposed) { + emitter.onError(error) + } + } + }, + { throwable -> + if (!emitter.isDisposed) { + emitter.onError(throwable) + } + }) { + if (!emitter.isDisposed) { + emitter.onComplete() + } + } + } + + @SuppressLint("MissingPermission") + override fun onDisposed(locationClient: GoogleApiClient) { + if (locationClient.isConnected) { + try { + fusedLocationProviderClient.setMockMode(false) + } catch (e: SecurityException) { + // if this happens then we couldn't have switched mock mode on in the first place, + // and the observer's onError will already have been called + } + } + if (mockLocationSubscription?.isDisposed != true) { + mockLocationSubscription?.dispose() + } + } + + companion object { + fun create( + fusedLocationProviderClient: FusedLocationProviderClient, + context: ObservableContext, + factory: ObservableFactory, + locationObservable: Observable, + ): Observable { + return factory.create( + MockLocationObservableOnSubscribe( + fusedLocationProviderClient, + context, + locationObservable + ) + ) + } + } +} \ No newline at end of file diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.java b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.java deleted file mode 100644 index 881e35a5..00000000 --- a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.java +++ /dev/null @@ -1,47 +0,0 @@ -package pl.charmas.android.reactivelocation2.observables.location; - -import android.app.PendingIntent; -import android.support.annotation.NonNull; - -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.LocationServices; - -import io.reactivex.Observable; -import io.reactivex.ObservableEmitter; -import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; -import pl.charmas.android.reactivelocation2.observables.ObservableContext; -import pl.charmas.android.reactivelocation2.observables.ObservableFactory; -import pl.charmas.android.reactivelocation2.observables.StatusException; - - -public class RemoveLocationIntentUpdatesObservableOnSubscribe extends BaseLocationObservableOnSubscribe { - private final PendingIntent intent; - - public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, PendingIntent intent) { - return factory.createObservable(new RemoveLocationIntentUpdatesObservableOnSubscribe(ctx, intent)); - } - - private RemoveLocationIntentUpdatesObservableOnSubscribe(ObservableContext ctx, PendingIntent intent) { - super(ctx); - this.intent = intent; - } - - @Override - protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { - LocationServices.FusedLocationApi.removeLocationUpdates(apiClient, intent) - .setResultCallback(new ResultCallback() { - @Override - public void onResult(@NonNull Status status) { - if (emitter.isDisposed()) return; - if (status.isSuccess()) { - emitter.onNext(status); - emitter.onComplete(); - } else { - emitter.onError(new StatusException(status)); - } - } - }); - } -} diff --git a/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.kt b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.kt new file mode 100644 index 00000000..c371debb --- /dev/null +++ b/android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.kt @@ -0,0 +1,50 @@ +package pl.charmas.android.reactivelocation2.observables.location + +import android.app.PendingIntent +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.FusedLocationProviderClient +import io.reactivex.Maybe +import io.reactivex.MaybeEmitter +import pl.charmas.android.reactivelocation2.observables.BaseLocationMaybeOnSubscribe +import pl.charmas.android.reactivelocation2.observables.MaybeContext +import pl.charmas.android.reactivelocation2.observables.MaybeFactory + +class RemoveLocationIntentUpdatesObservableOnSubscribe private constructor( + private val fusedLocationProviderClient: FusedLocationProviderClient, + ctx: MaybeContext, + private val intent: PendingIntent +) : BaseLocationMaybeOnSubscribe(ctx) { + override fun onGoogleApiClientReady( + apiClient: GoogleApiClient, + emitter: MaybeEmitter + ) { + fusedLocationProviderClient.removeLocationUpdates(intent) + .addOnCompleteListener { + if (!emitter.isDisposed) { + emitter.onSuccess(true) + } + } + .addOnFailureListener { error -> + if (!emitter.isDisposed) { + emitter.onError(error) + } + } + } + + companion object { + fun create( + fusedLocationProviderClient: FusedLocationProviderClient, + ctx: MaybeContext, + factory: MaybeFactory, + intent: PendingIntent + ): Maybe { + return factory.create( + RemoveLocationIntentUpdatesObservableOnSubscribe( + fusedLocationProviderClient, + ctx, + intent + ) + ) + } + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 1c6739a9..22f8808f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,21 +1,24 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.4.30' repositories { mavenCentral() jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.android.tools.build:gradle:4.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } // To avoid manually setting the same values in all Android modules, set the value on the root // project and then reference this from the modules ext { - compileSdkVersion = 25 - targetSdkVersion = 25 - buildToolsVersion = "25.0.3" + compileSdkVersion = 28 + targetSdkVersion = 26 + buildToolsVersion = "28.0.3" } def isReleaseBuild() { @@ -29,5 +32,6 @@ allprojects { repositories { mavenCentral() jcenter() + google() } } diff --git a/gradle.properties b/gradle.properties index a0a4da92..9ba32d77 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,5 @@ +android.useAndroidX=true + VERSION_NAME=2.1 VERSION_CODE=102 GROUP=pl.charmas.android diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cbb3db13..cc6052d4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Sep 29 10:11:52 CEST 2017 +#Thu Oct 15 14:02:00 MSK 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/maven_push.gradle b/maven_push.gradle index 712abf68..9350cea1 100644 --- a/maven_push.gradle +++ b/maven_push.gradle @@ -92,6 +92,11 @@ afterEvaluate { project -> sign configurations.archives } + task androidSourcesJar(type: Jar) { + classifier = 'sources' + from android.sourceSets.main.java.sourceFiles + } + task androidJavadocs(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) @@ -103,11 +108,6 @@ afterEvaluate { project -> from androidJavadocs.destinationDir } - task androidSourcesJar(type: Jar) { - classifier = 'sources' - from android.sourceSets.main.java.sourceFiles - } - artifacts { archives androidSourcesJar archives androidJavadocsJar diff --git a/sample/build.gradle b/sample/build.gradle index ddb75b0e..2cf35d70 100755 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,8 +1,10 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' def getGooglePlayServicesApiKey() { if (REACTIVE_LOCATION_GMS_API_KEY != null && !REACTIVE_LOCATION_GMS_API_KEY.isEmpty()) { - return REACTIVE_LOCATION_GMS_API_KEY; + return REACTIVE_LOCATION_GMS_API_KEY } return "" } @@ -12,7 +14,7 @@ android { buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { - minSdkVersion 14 + minSdkVersion 19 targetSdkVersion rootProject.ext.targetSdkVersion versionName project.VERSION_NAME versionCode Integer.parseInt(project.VERSION_CODE) @@ -20,8 +22,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } buildTypes { release { @@ -35,8 +37,18 @@ android { } dependencies { - compile 'com.android.support:appcompat-v7:25.3.1' - compile 'io.reactivex.rxjava2:rxandroid:2.0.1' - compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.1@aar' - compile project(':android-reactive-location') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + implementation "androidx.core:core-ktx:1.3.1" + + implementation 'androidx.appcompat:appcompat:1.1.0' + + implementation('com.google.android.gms:play-services-location:17.0.0') { + exclude group: 'com.google.android.gms', module: 'play-services-places' + } + implementation 'com.google.android.libraries.places:places:2.3.0' + implementation 'io.reactivex.rxjava2:rxjava:2.2.19' + implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' + implementation 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.1@aar' + + implementation project(':android-reactive-location') } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index c7122be9..66125344 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -3,9 +3,10 @@ xmlns:tools="http://schemas.android.com/tools" package="pl.charmas.android.reactivelocation2.sample"> + + - + - + - + - + - + - + diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.java deleted file mode 100755 index 70a45c5c..00000000 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.java +++ /dev/null @@ -1,31 +0,0 @@ -package pl.charmas.android.reactivelocation2.sample; - -import android.Manifest; -import android.support.v7.app.AppCompatActivity; -import android.widget.Toast; - -import com.tbruyelle.rxpermissions2.RxPermissions; - -import io.reactivex.functions.Consumer; - -public abstract class BaseActivity extends AppCompatActivity { - - @Override - protected void onStart() { - super.onStart(); - new RxPermissions(this) - .request(Manifest.permission.ACCESS_FINE_LOCATION) - .subscribe(new Consumer() { - @Override - public void accept(Boolean granted) throws Exception { - if (granted) { - onLocationPermissionGranted(); - } else { - Toast.makeText(BaseActivity.this, "Sorry, no demo without permission...", Toast.LENGTH_SHORT).show(); - } - } - }); - } - - protected abstract void onLocationPermissionGranted(); -} diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.kt new file mode 100755 index 00000000..07424a13 --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.kt @@ -0,0 +1,36 @@ +package pl.charmas.android.reactivelocation2.sample + +import android.Manifest +import androidx.appcompat.app.AppCompatActivity +import com.tbruyelle.rxpermissions2.RxPermissions +import io.reactivex.disposables.CompositeDisposable +import pl.charmas.android.reactivelocation2.sample.ext.toast + +abstract class BaseActivity : AppCompatActivity() { + + var disposables = CompositeDisposable() + + override fun onStart() { + super.onStart() + val subscribe = RxPermissions(this) + .request( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + .reduce { t1, t2 -> t1 && t2 } + .subscribe { granted: Boolean -> + if (granted) { + onLocationPermissionGranted() + } else { + toast("Sorry, no demo without permission...") + } + } + } + + override fun onStop() { + super.onStop() + disposables.clear() + } + + protected abstract fun onLocationPermissionGranted() +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java index 51d498bb..031ed8dc 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java @@ -1,25 +1,19 @@ package pl.charmas.android.reactivelocation2.sample; +import android.annotation.SuppressLint; import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; import android.util.Log; -import android.view.View; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; - -import com.google.android.gms.common.api.Status; import com.google.android.gms.location.Geofence; import com.google.android.gms.location.GeofencingRequest; - -import io.reactivex.Observable; import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Consumer; -import io.reactivex.functions.Function; import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; +import pl.charmas.android.reactivelocation2.sample.ext.LocationExtKt; import pl.charmas.android.reactivelocation2.sample.utils.DisplayTextOnViewAction; -import pl.charmas.android.reactivelocation2.sample.utils.LocationToStringFunc; import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; @@ -36,35 +30,26 @@ public class GeofenceActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - reactiveLocationProvider = new ReactiveLocationProvider(this); + reactiveLocationProvider = new ReactiveLocationProvider(this, getString(R.string.API_KEY)); setContentView(R.layout.activity_geofence); initViews(); } private void initViews() { - lastKnownLocationView = (TextView) findViewById(R.id.last_known_location_view); - latitudeInput = (EditText) findViewById(R.id.latitude_input); - longitudeInput = (EditText) findViewById(R.id.longitude_input); - radiusInput = (EditText) findViewById(R.id.radius_input); - findViewById(R.id.add_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - addGeofence(); - } - }); - findViewById(R.id.clear_button).setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - clearGeofence(); - } - }); + lastKnownLocationView = findViewById(R.id.last_known_location_view); + latitudeInput = findViewById(R.id.latitude_input); + longitudeInput = findViewById(R.id.longitude_input); + radiusInput = findViewById(R.id.radius_input); + findViewById(R.id.add_button).setOnClickListener(v -> addGeofence()); + findViewById(R.id.clear_button).setOnClickListener(v -> clearGeofence()); } + @SuppressLint("MissingPermission") @Override protected void onLocationPermissionGranted() { lastKnownLocationDisposable = reactiveLocationProvider .getLastKnownLocation() - .map(new LocationToStringFunc()) + .map(LocationExtKt::text) .subscribe(new DisplayTextOnViewAction(lastKnownLocationView)); } @@ -75,20 +60,13 @@ protected void onStop() { } private void clearGeofence() { - reactiveLocationProvider + Disposable subscribe = reactiveLocationProvider .removeGeofences(createNotificationBroadcastPendingIntent()) - .subscribe(new Consumer() { - @Override - public void accept(Status status) throws Exception { - toast("Geofences removed"); - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - toast("Error removing geofences"); - Log.d(TAG, "Error removing geofences", throwable); - } - }); + .subscribe( + status -> toast("Geofences removed"), throwable -> { + toast("Error removing geofences"); + Log.d(TAG, "Error removing geofences", throwable); + }); } private void toast(String text) { @@ -99,33 +77,21 @@ private PendingIntent createNotificationBroadcastPendingIntent() { return PendingIntent.getBroadcast(this, 0, new Intent(this, GeofenceBroadcastReceiver.class), PendingIntent.FLAG_UPDATE_CURRENT); } + @SuppressLint("MissingPermission") private void addGeofence() { final GeofencingRequest geofencingRequest = createGeofencingRequest(); if (geofencingRequest == null) return; final PendingIntent pendingIntent = createNotificationBroadcastPendingIntent(); - reactiveLocationProvider + Disposable subscribe = reactiveLocationProvider .removeGeofences(pendingIntent) - .flatMap(new Function>() { - @Override - public Observable apply(Status status) throws Exception { - return reactiveLocationProvider.addGeofences(pendingIntent, geofencingRequest); - } - - }) - .subscribe(new Consumer() { - @Override - public void accept(Status addGeofenceResult) { - toast("Geofence added, success: " + addGeofenceResult.isSuccess()); - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) { - toast("Error adding geofence."); - Log.d(TAG, "Error adding geofence.", throwable); - } - }); + .flatMap(status -> reactiveLocationProvider.addGeofences(pendingIntent, geofencingRequest)) + .subscribe(addGeofenceResult -> toast("Geofence added, success: " + addGeofenceResult) + , throwable -> { + toast("Error adding geofence."); + Log.d(TAG, "Error adding geofence.", throwable); + }); } private GeofencingRequest createGeofencingRequest() { diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceBroadcastReceiver.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceBroadcastReceiver.java index 8b1c5c44..931de375 100644 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceBroadcastReceiver.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceBroadcastReceiver.java @@ -5,8 +5,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.support.v4.app.NotificationCompat; - +import androidx.core.app.NotificationCompat; import com.google.android.gms.location.Geofence; import com.google.android.gms.location.GeofencingEvent; diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java deleted file mode 100755 index 6ab3f2b9..00000000 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java +++ /dev/null @@ -1,232 +0,0 @@ -package pl.charmas.android.reactivelocation2.sample; - -import android.content.Intent; -import android.content.IntentSender; -import android.location.Address; -import android.location.Location; -import android.os.Bundle; -import android.text.TextUtils; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.TextView; -import android.widget.Toast; - -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.ActivityRecognitionResult; -import com.google.android.gms.location.LocationRequest; -import com.google.android.gms.location.LocationSettingsRequest; -import com.google.android.gms.location.LocationSettingsResult; -import com.google.android.gms.location.LocationSettingsStates; -import com.google.android.gms.location.LocationSettingsStatusCodes; - -import java.util.List; - -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Consumer; -import io.reactivex.functions.Function; -import io.reactivex.schedulers.Schedulers; -import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; -import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration; -import pl.charmas.android.reactivelocation2.sample.utils.AddressToStringFunc; -import pl.charmas.android.reactivelocation2.sample.utils.DetectedActivityToString; -import pl.charmas.android.reactivelocation2.sample.utils.DisplayTextOnViewAction; -import pl.charmas.android.reactivelocation2.sample.utils.LocationToStringFunc; -import pl.charmas.android.reactivelocation2.sample.utils.ToMostProbableActivity; - -import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; - -public class MainActivity extends BaseActivity { - private final static int REQUEST_CHECK_SETTINGS = 0; - private final static String TAG = "MainActivity"; - private ReactiveLocationProvider locationProvider; - - private TextView lastKnownLocationView; - private TextView updatableLocationView; - private TextView addressLocationView; - private TextView currentActivityView; - - private Observable lastKnownLocationObservable; - private Observable locationUpdatesObservable; - private Observable activityObservable; - - private Disposable lastKnownLocationDisposable; - private Disposable updatableLocationDisposable; - private Disposable addressDisposable; - private Disposable activityDisposable; - private Observable addressObservable; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - - lastKnownLocationView = (TextView) findViewById(R.id.last_known_location_view); - updatableLocationView = (TextView) findViewById(R.id.updated_location_view); - addressLocationView = (TextView) findViewById(R.id.address_for_location_view); - currentActivityView = (TextView) findViewById(R.id.activity_recent_view); - - locationProvider = new ReactiveLocationProvider(getApplicationContext(), ReactiveLocationProviderConfiguration - .builder() - .setRetryOnConnectionSuspended(true) - .build() - ); - - lastKnownLocationObservable = locationProvider - .getLastKnownLocation() - .observeOn(AndroidSchedulers.mainThread()); - - final LocationRequest locationRequest = LocationRequest.create() - .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) - .setNumUpdates(5) - .setInterval(100); - locationUpdatesObservable = locationProvider - .checkLocationSettings( - new LocationSettingsRequest.Builder() - .addLocationRequest(locationRequest) - .setAlwaysShow(true) //Refrence: http://stackoverflow.com/questions/29824408/google-play-services-locationservices-api-new-option-never - .build() - ) - .doOnNext(new Consumer() { - @Override - public void accept(LocationSettingsResult locationSettingsResult) { - Status status = locationSettingsResult.getStatus(); - if (status.getStatusCode() == LocationSettingsStatusCodes.RESOLUTION_REQUIRED) { - try { - status.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS); - } catch (IntentSender.SendIntentException th) { - Log.e("MainActivity", "Error opening settings activity.", th); - } - } - } - }) - .flatMap(new Function>() { - @Override - public Observable apply(LocationSettingsResult locationSettingsResult) { - return locationProvider.getUpdatedLocation(locationRequest); - } - }) - .observeOn(AndroidSchedulers.mainThread()); - - addressObservable = locationProvider.getUpdatedLocation(locationRequest) - .flatMap(new Function>>() { - @Override - public Observable> apply(Location location) { - return locationProvider.getReverseGeocodeObservable(location.getLatitude(), location.getLongitude(), 1); - } - }) - .map(new Function, Address>() { - @Override - public Address apply(List
addresses) { - return addresses != null && !addresses.isEmpty() ? addresses.get(0) : null; - } - }) - .map(new AddressToStringFunc()) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()); - - activityObservable = locationProvider - .getDetectedActivity(50) - .observeOn(AndroidSchedulers.mainThread()); - } - - @Override - protected void onLocationPermissionGranted() { - lastKnownLocationDisposable = lastKnownLocationObservable - .map(new LocationToStringFunc()) - .subscribe(new DisplayTextOnViewAction(lastKnownLocationView), new ErrorHandler()); - - updatableLocationDisposable = locationUpdatesObservable - .map(new LocationToStringFunc()) - .map(new Function() { - int count = 0; - - @Override - public String apply(String s) { - return s + " " + count++; - } - }) - .subscribe(new DisplayTextOnViewAction(updatableLocationView), new ErrorHandler()); - - - addressDisposable = addressObservable - .subscribe(new DisplayTextOnViewAction(addressLocationView), new ErrorHandler()); - - activityDisposable = activityObservable - .map(new ToMostProbableActivity()) - .map(new DetectedActivityToString()) - .subscribe(new DisplayTextOnViewAction(currentActivityView), new ErrorHandler()); - } - - @Override - protected void onStop() { - super.onStop(); - dispose(updatableLocationDisposable); - dispose(addressDisposable); - dispose(lastKnownLocationDisposable); - dispose(activityDisposable); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - menu.add("Geofencing").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - startActivity(new Intent(MainActivity.this, GeofenceActivity.class)); - return true; - } - }); - menu.add("Places").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - if (TextUtils.isEmpty(getString(R.string.API_KEY))) { - Toast.makeText(MainActivity.this, "First you need to configure your API Key - see README.md", Toast.LENGTH_SHORT).show(); - } else { - startActivity(new Intent(MainActivity.this, PlacesActivity.class)); - } - return true; - } - }); - menu.add("Mock Locations").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - startActivity(new Intent(MainActivity.this, MockLocationsActivity.class)); - return true; - } - }); - return true; - } - - private class ErrorHandler implements Consumer { - @Override - public void accept(Throwable throwable) { - Toast.makeText(MainActivity.this, "Error occurred.", Toast.LENGTH_SHORT).show(); - Log.d("MainActivity", "Error occurred", throwable); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - final LocationSettingsStates states = LocationSettingsStates.fromIntent(data);//intent); - switch (requestCode) { - case REQUEST_CHECK_SETTINGS: - //Reference: https://developers.google.com/android/reference/com/google/android/gms/location/SettingsApi - switch (resultCode) { - case RESULT_OK: - // All required changes were successfully made - Log.d(TAG, "User enabled location"); - break; - case RESULT_CANCELED: - // The user was asked to change settings, but chose not to - Log.d(TAG, "User Cancelled enabling location"); - break; - default: - break; - } - break; - } - } - -} diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.kt new file mode 100755 index 00000000..83a4943e --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.kt @@ -0,0 +1,321 @@ +package pl.charmas.android.reactivelocation2.sample + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.content.IntentSender.SendIntentException +import android.location.LocationManager +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.widget.TextView +import com.google.android.gms.common.api.ApiException +import com.google.android.gms.common.api.ResolvableApiException +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationSettingsRequest +import com.google.android.gms.location.LocationSettingsResponse +import com.google.android.gms.location.LocationSettingsStates +import com.google.android.gms.location.LocationSettingsStatusCodes +import io.reactivex.Maybe +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.functions.Consumer +import io.reactivex.schedulers.Schedulers +import pl.charmas.android.reactivelocation2.ReactiveLocationProvider +import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration.Companion.builder +import pl.charmas.android.reactivelocation2.sample.ext.addTo +import pl.charmas.android.reactivelocation2.sample.ext.addressToString +import pl.charmas.android.reactivelocation2.sample.ext.text +import pl.charmas.android.reactivelocation2.sample.ext.toast +import pl.charmas.android.reactivelocation2.sample.utils.DetectedActivityToString +import pl.charmas.android.reactivelocation2.sample.utils.DisplayTextOnViewAction +import pl.charmas.android.reactivelocation2.sample.utils.ToMostProbableActivity +import java.util.Locale +import java.util.concurrent.TimeUnit + +class MainActivity : BaseActivity() { + + private val lastKnownLocationView: TextView by lazy { + findViewById(R.id.last_known_location_view) + } + + private val lastBestProviderLocationView: TextView by lazy { + findViewById(R.id.last_best_provider_location_view) + } + + private val locationAllProvidersView: TextView by lazy { + findViewById(R.id.locationAllProviders) + } + private val locationNetworkProviderView: TextView by lazy { + findViewById(R.id.locationNetworkProviderView) + } + + private val locationNetworkProviderUpdatesView: TextView by lazy { + findViewById(R.id.locationNetworkProviderUpdatesView) + } + + private val updatableLocationView: TextView by lazy { + findViewById(R.id.updated_location_view) + } + + private val addressLocationView: TextView by lazy { + findViewById(R.id.address_for_location_view) + } + + private val currentActivityView: TextView by lazy { + findViewById(R.id.activity_recent_view) + } + + private val rxLocationProvider: ReactiveLocationProvider by lazy { + ReactiveLocationProvider( + applicationContext, + getString(R.string.API_KEY), + builder() + .setRetryOnConnectionSuspended(true) + .build() + ) + } + + private val locationRequest: LocationRequest = LocationRequest.create() + .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY) + .setInterval(TimeUnit.SECONDS.toMillis(1)) + + @SuppressLint("MissingPermission") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + } + + override fun onLocationPermissionGranted() { + lastKnownLocationSubscribe() + locationBestProviderSubscribe() + locationAllProvidersMaybeSubscribe() + lastLocationNetworkProviderMaybeSubscribe() + updatedLocationNetworkProviderSubscribe() + updatedLocationSubscribe() + addressObservableSubscribe() + activityObservableSubscribe() + } + + private fun activityObservableSubscribe() { + rxLocationProvider + .getDetectedActivity(50) + .observeOn(AndroidSchedulers.mainThread()) + .map(ToMostProbableActivity()) + .map(DetectedActivityToString()) + .subscribe( + DisplayTextOnViewAction(currentActivityView), + ErrorHandler("activityObservable") + ) + .addTo(disposables) + } + + @SuppressLint("MissingPermission") + private fun addressObservableSubscribe() { + rxLocationProvider.getUpdatedLocation(locationRequest) + .flatMapMaybe { location -> + rxLocationProvider.getReverseGeocodeMaybe( + Locale.getDefault(), + location.latitude, + location.longitude, + 1 + ) + } + .map { addresses -> + val address = addresses.firstOrNull() + address?.addressToString() ?: "" + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + DisplayTextOnViewAction(addressLocationView), + ErrorHandler("addressObservable") + ) + .addTo(disposables) + } + + @SuppressLint("MissingPermission") + private fun updatedLocationSubscribe() { + var count = 0 + + rxLocationProvider + .checkLocationSettings( + LocationSettingsRequest.Builder() + .addLocationRequest(locationRequest) + .setAlwaysShow(true) //Refrence: http://stackoverflow.com/questions/29824408/google-play-services-locationservices-api-new-option-never + .build() + ) + .doOnSuccess { locationSettingsResponse: LocationSettingsResponse -> + Log.d( + "MainActivity", + "getLocationSettingsStates isGpsUsable = " + locationSettingsResponse.locationSettingsStates + .isGpsUsable + ) + } + .doOnError { throwable: Throwable -> + val statusCode = (throwable as ApiException).statusCode + when (statusCode) { + LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try { + // Show the dialog by calling startResolutionForResult(), and check the + // result in onActivityResult(). + val rae = throwable as ResolvableApiException + rae.startResolutionForResult( + this@MainActivity, + REQUEST_CHECK_SETTINGS + ) + } catch (sie: SendIntentException) { + Log.i( + TAG, + "PendingIntent unable to execute request." + ) + } + LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> { + val errorMessage = + "Location settings are inadequate, and cannot be " + + "fixed here. Fix in Settings." + Log.e(TAG, errorMessage) + toast(errorMessage) + } + } + } + .flatMapObservable { rxLocationProvider.getUpdatedLocation(locationRequest) } + .observeOn(AndroidSchedulers.mainThread()) + .map { location -> location.text() + " " + count++ } + .subscribe( + DisplayTextOnViewAction(updatableLocationView), + Consumer { throwable -> + if (throwable is ResolvableApiException) { + return@Consumer + } + + toast("Error occurred.") + Log.d("MainActivity", "Error occurred locationUpdatesObservable:", throwable) + } + ) + .addTo(disposables) + } + + private fun lastKnownLocationSubscribe() { + Maybe.timer(100, TimeUnit.MILLISECONDS, Schedulers.io()) + .flatMap { rxLocationProvider.lastKnownLocation } + .subscribeOn(Schedulers.io()) + .map { it.text() } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + DisplayTextOnViewAction(lastKnownLocationView), + ErrorHandler("lastKnownLocationMaybe") + ) + .addTo(disposables) + } + + private fun locationBestProviderSubscribe() { + Maybe.timer(100, TimeUnit.MILLISECONDS, Schedulers.io()) + .flatMap { rxLocationProvider.locationBestProvider } + .subscribeOn(Schedulers.io()) + .map { it.text() } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + DisplayTextOnViewAction(lastBestProviderLocationView), + ErrorHandler("locationBestProviderMaybe") + ) + .addTo(disposables) + } + + @SuppressLint("MissingPermission") + private fun locationAllProvidersMaybeSubscribe() { + Maybe.timer(100, TimeUnit.MILLISECONDS, Schedulers.io()) + .flatMap { rxLocationProvider.getLastKnownLocationFromAllProviders(1, TimeUnit.SECONDS, Schedulers.io()) } + .subscribeOn(Schedulers.io()) + .map { it.text() } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + DisplayTextOnViewAction(locationAllProvidersView), + ErrorHandler("locationBestProviderMaybe") + ) + .addTo(disposables) + } + + private fun lastLocationNetworkProviderMaybeSubscribe() { + Maybe.timer(100, TimeUnit.MILLISECONDS, Schedulers.io()) + .flatMap { + rxLocationProvider.getLastKnownLocationFromProvider(LocationManager.NETWORK_PROVIDER) + } + .observeOn(Schedulers.io()) + .map { it.text() } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + DisplayTextOnViewAction(locationNetworkProviderView), + ErrorHandler("locationNetworkProviderMaybe") + ) + .addTo(disposables) + } + + private fun updatedLocationNetworkProviderSubscribe() { + var count = 0 + + Maybe.timer(100, TimeUnit.MILLISECONDS, Schedulers.io()) + .flatMapObservable { + rxLocationProvider.getLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) + } + .observeOn(Schedulers.io()) + .map { location -> location.text() + " " + count++ } + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + DisplayTextOnViewAction(locationNetworkProviderUpdatesView), + ErrorHandler("updatedLocationNetworkProviderSubscribe") + ) + .addTo(disposables) + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menu.add("Geofencing") + .setOnMenuItemClickListener { + startActivity(Intent(this@MainActivity, GeofenceActivity::class.java)) + true + } + menu.add("Places") + .setOnMenuItemClickListener { + if (getString(R.string.API_KEY).isEmpty()) { + toast("First you need to configure your API Key - see README.md") + } else { + startActivity(Intent(this@MainActivity, PlacesActivity::class.java)) + } + true + } + menu.add("Mock Locations") + .setOnMenuItemClickListener { + startActivity(Intent(this@MainActivity, MockLocationsActivity::class.java)) + true + } + return true + } + + private inner class ErrorHandler(val source: String) : Consumer { + override fun accept(throwable: Throwable) { + toast("Error occurred.") + Log.d("MainActivity", "Error occurred $source:", throwable) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + val states = LocationSettingsStates.fromIntent(data) //intent); + when (requestCode) { + REQUEST_CHECK_SETTINGS -> when (resultCode) { + Activity.RESULT_OK -> // All required changes were successfully made + Log.d(TAG, "User enabled location") + Activity.RESULT_CANCELED -> // The user was asked to change settings, but chose not to + Log.d( + TAG, + "User Cancelled enabling location" + ) + else -> { + } + } + } + } + + companion object { + private const val REQUEST_CHECK_SETTINGS = 0 + private const val TAG = "MainActivity" + } +} diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java index c529304f..3963ebb5 100755 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java @@ -1,12 +1,12 @@ package pl.charmas.android.reactivelocation2.sample; import android.Manifest; +import android.annotation.SuppressLint; import android.content.pm.PackageManager; import android.location.Location; import android.os.Build; import android.os.Bundle; import android.os.SystemClock; -import android.support.v4.app.ActivityCompat; import android.util.Log; import android.view.View; import android.widget.Button; @@ -15,12 +15,8 @@ import android.widget.TextView; import android.widget.Toast; import android.widget.ToggleButton; - -import com.google.android.gms.common.api.Status; +import androidx.core.app.ActivityCompat; import com.google.android.gms.location.LocationRequest; - -import java.util.Date; - import io.reactivex.Observable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.BiFunction; @@ -28,8 +24,10 @@ import io.reactivex.functions.Function; import io.reactivex.subjects.PublishSubject; import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; +import pl.charmas.android.reactivelocation2.sample.ext.LocationExtKt; import pl.charmas.android.reactivelocation2.sample.utils.DisplayTextOnViewAction; -import pl.charmas.android.reactivelocation2.sample.utils.LocationToStringFunc; + +import java.util.Date; import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; @@ -55,7 +53,7 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mocklocations); - locationProvider = new ReactiveLocationProvider(this); + locationProvider = new ReactiveLocationProvider(this,getString(R.string.API_KEY)); mockLocationSubject = PublishSubject.create(); mockLocationObservable = mockLocationSubject.hide(); @@ -100,7 +98,7 @@ protected void onLocationPermissionGranted() { updatedLocationDisposable = locationProvider .getUpdatedLocation(locationRequest) - .map(new LocationToStringFunc()) + .map(LocationExtKt::text) .map(new Function() { int count = 0; @@ -120,6 +118,7 @@ private void addMockLocation() { } } + @SuppressLint("MissingPermission") private void setMockMode(boolean toggle) { if (toggle) { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { @@ -127,13 +126,14 @@ private void setMockMode(boolean toggle) { } mockLocationDisposable = - Observable.zip(locationProvider.mockLocation(mockLocationObservable), - mockLocationObservable, new BiFunction() { + Observable.zip( + locationProvider.mockLocation(mockLocationObservable), + mockLocationObservable, new BiFunction() { int count = 0; @Override - public String apply(Status result, Location location) { - return new LocationToStringFunc().apply(location) + " " + count++; + public String apply(Boolean result, Location location) { + return LocationExtKt.text(location) + " " + count++; } }) .subscribe(new DisplayTextOnViewAction(mockLocationView), new ErrorHandler()); diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.java deleted file mode 100755 index 26966661..00000000 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.java +++ /dev/null @@ -1,165 +0,0 @@ -package pl.charmas.android.reactivelocation2.sample; - -import android.location.Location; -import android.os.Bundle; -import android.text.TextUtils; -import android.util.Log; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.TextView; - -import com.google.android.gms.location.places.AutocompletePrediction; -import com.google.android.gms.location.places.AutocompletePredictionBuffer; -import com.google.android.gms.location.places.PlaceLikelihood; -import com.google.android.gms.location.places.PlaceLikelihoodBuffer; -import com.google.android.gms.maps.model.LatLng; -import com.google.android.gms.maps.model.LatLngBounds; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import io.reactivex.Observable; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.functions.BiFunction; -import io.reactivex.functions.Consumer; -import io.reactivex.functions.Function; -import io.reactivex.functions.Predicate; -import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; -import pl.charmas.android.reactivelocation2.sample.utils.RxTextView; - -import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; - -public class PlacesActivity extends BaseActivity { - - private TextView currentPlaceView; - private EditText queryView; - private ListView placeSuggestionsList; - private ReactiveLocationProvider reactiveLocationProvider; - private CompositeDisposable compositeDisposable; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_places); - currentPlaceView = (TextView) findViewById(R.id.current_place_view); - queryView = (EditText) findViewById(R.id.place_query_view); - placeSuggestionsList = (ListView) findViewById(R.id.place_suggestions_list); - placeSuggestionsList.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - AutocompleteInfo info = (AutocompleteInfo) parent.getAdapter().getItem(position); - startActivity(PlacesResultActivity.getStartIntent(PlacesActivity.this, info.id)); - } - }); - - reactiveLocationProvider = new ReactiveLocationProvider(this); - } - - @Override - protected void onLocationPermissionGranted() { - compositeDisposable = new CompositeDisposable(); - compositeDisposable.add( - reactiveLocationProvider.getCurrentPlace(null) - .subscribe(new Consumer() { - @Override - public void accept(PlaceLikelihoodBuffer buffer) { - PlaceLikelihood likelihood = buffer.get(0); - if (likelihood != null) { - currentPlaceView.setText(likelihood.getPlace().getName()); - } - buffer.release(); - } - }, new Consumer() { - @Override - public void accept(Throwable throwable) throws Exception { - Log.e("PlacesActivity", "Error in observable", throwable); - } - }) - ); - - Observable queryObservable = RxTextView - .textChanges(queryView) - .map(new Function() { - @Override - public String apply(CharSequence charSequence) { - return charSequence.toString(); - } - }) - .debounce(1, TimeUnit.SECONDS) - .filter(new Predicate() { - @Override - public boolean test(String s) { - return !TextUtils.isEmpty(s); - } - }); - Observable lastKnownLocationObservable = reactiveLocationProvider.getLastKnownLocation(); - Observable suggestionsObservable = Observable - .combineLatest(queryObservable, lastKnownLocationObservable, - new BiFunction() { - @Override - public QueryWithCurrentLocation apply(String query, Location currentLocation) { - return new QueryWithCurrentLocation(query, currentLocation); - } - }).flatMap(new Function>() { - @Override - public Observable apply(QueryWithCurrentLocation q) { - if (q.location == null) return Observable.empty(); - - double latitude = q.location.getLatitude(); - double longitude = q.location.getLongitude(); - LatLngBounds bounds = new LatLngBounds( - new LatLng(latitude - 0.05, longitude - 0.05), - new LatLng(latitude + 0.05, longitude + 0.05) - ); - return reactiveLocationProvider.getPlaceAutocompletePredictions(q.query, bounds, null); - } - }); - - compositeDisposable.add(suggestionsObservable.subscribe(new Consumer() { - @Override - public void accept(AutocompletePredictionBuffer buffer) { - List infos = new ArrayList<>(); - for (AutocompletePrediction prediction : buffer) { - infos.add(new AutocompleteInfo(prediction.getFullText(null).toString(), prediction.getPlaceId())); - } - buffer.release(); - placeSuggestionsList.setAdapter(new ArrayAdapter<>(PlacesActivity.this, android.R.layout.simple_list_item_1, infos)); - } - })); - } - - @Override - protected void onStop() { - super.onStop(); - dispose(compositeDisposable); - } - - private static class QueryWithCurrentLocation { - final String query; - public final Location location; - - private QueryWithCurrentLocation(String query, Location location) { - this.query = query; - this.location = location; - } - } - - private static class AutocompleteInfo { - private final String description; - private final String id; - - private AutocompleteInfo(String description, String id) { - this.description = description; - this.id = id; - } - - @Override - public String toString() { - return description; - } - } -} diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt new file mode 100755 index 00000000..55c5e18f --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.kt @@ -0,0 +1,139 @@ +package pl.charmas.android.reactivelocation2.sample + +import android.annotation.SuppressLint +import android.location.Location +import android.os.Bundle +import android.util.Log +import android.widget.AdapterView.OnItemClickListener +import android.widget.ArrayAdapter +import android.widget.EditText +import android.widget.ListView +import android.widget.TextView +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.google.android.libraries.places.api.Places +import com.google.android.libraries.places.api.model.RectangularBounds +import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest +import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest +import io.reactivex.Observable +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.functions.BiFunction +import pl.charmas.android.reactivelocation2.ReactiveLocationProvider +import pl.charmas.android.reactivelocation2.sample.utils.RxTextView +import pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent +import java.util.concurrent.TimeUnit + +class PlacesActivity : BaseActivity() { + + private lateinit var currentPlaceView: TextView + private lateinit var queryView: EditText + private lateinit var placeSuggestionsList: ListView + private lateinit var reactiveLocationProvider: ReactiveLocationProvider + private var compositeDisposable: CompositeDisposable = CompositeDisposable() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_places) + Places.initialize(this.applicationContext, getString(R.string.API_KEY)) + currentPlaceView = findViewById(R.id.current_place_view) + queryView = findViewById(R.id.place_query_view) + placeSuggestionsList = findViewById(R.id.place_suggestions_list) + placeSuggestionsList.onItemClickListener = + OnItemClickListener { parent, view, position, id -> + val info = + parent.adapter.getItem(position) as AutocompleteInfo + val placeId = info.id + if (placeId != null) { + startActivity(PlacesResultActivity.getStartIntent(this@PlacesActivity, placeId)) + } + } + reactiveLocationProvider = ReactiveLocationProvider(this,getString(R.string.API_KEY)) + } + + @SuppressLint("MissingPermission") + override fun onLocationPermissionGranted() { + compositeDisposable.add( + reactiveLocationProvider.getCurrentPlace( + FindCurrentPlaceRequest.builder(listOf(com.google.android.libraries.places.api.model.Place.Field.ID)) + .build() + ) + .subscribe({ response -> + val buffer = response.placeLikelihoods + val likelihood = buffer.firstOrNull() + if (likelihood != null) { + currentPlaceView.text = likelihood.place.name + } + }) { throwable -> + Log.e("PlacesActivity", "Error in observable", throwable) + } + ) + val queryObservable = RxTextView + .textChanges(queryView) + .map { charSequence -> charSequence.toString() } + .debounce(1, TimeUnit.SECONDS) + .filter { s -> s.isNotEmpty() } + + compositeDisposable.add( + Observable.combineLatest( + queryObservable, + reactiveLocationProvider.lastKnownLocation.toObservable(), + BiFunction { query, currentLocation -> + QueryWithCurrentLocation(query, currentLocation) + } + ) + .flatMapMaybe { q -> + val latitude = q.location.latitude + val longitude = q.location.longitude + val bounds = LatLngBounds( + LatLng(latitude - 50.05, longitude - 50.05), + LatLng(latitude + 50.05, longitude + 50.05) + ) + reactiveLocationProvider.getPlaceAutocompletePredictions( + FindAutocompletePredictionsRequest.builder() + .setQuery(q.query) + .setLocationRestriction(RectangularBounds.newInstance(bounds)) + .build() + ) + } + .doOnError { Log.e(TAG, "onLocationPermissionGranted (line 80): ", it) } + .retry() + .subscribe { buffer -> + val infos = mutableListOf() + for (prediction in buffer) { + infos.add( + + AutocompleteInfo( + prediction.getFullText(null).toString(), + prediction.placeId + ) + ) + } + placeSuggestionsList.adapter = ArrayAdapter( + this@PlacesActivity, + android.R.layout.simple_list_item_1, + infos + ) + } + ) + } + + override fun onStop() { + super.onStop() + UnsubscribeIfPresent.dispose(compositeDisposable) + } + + data class QueryWithCurrentLocation(val query: String, val location: Location) + + data class AutocompleteInfo( + val description: String, + val id: String? + ) { + override fun toString(): String { + return description + } + } + + companion object { + const val TAG: String = "PlacesActivity" + } +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.java deleted file mode 100755 index cabde5d7..00000000 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.java +++ /dev/null @@ -1,82 +0,0 @@ -package pl.charmas.android.reactivelocation2.sample; - -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.widget.TextView; - -import com.google.android.gms.location.places.Place; -import com.google.android.gms.location.places.PlaceBuffer; - -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.functions.Consumer; -import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; - -import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; - -public class PlacesResultActivity extends BaseActivity { - - private static final String EXTRA_PLACE_ID = "EXTRA_PLACE_ID"; - - private ReactiveLocationProvider reactiveLocationProvider; - private CompositeDisposable compositeSubscription; - private TextView placeNameView; - private TextView placeLocationView; - private TextView placeAddressView; - private String placeId; - - public static Intent getStartIntent(Context context, @NonNull String placeId) { - Intent startIntent = new Intent(context, PlacesResultActivity.class); - startIntent.putExtra(EXTRA_PLACE_ID, placeId); - - return startIntent; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_places_result); - - placeNameView = (TextView) findViewById(R.id.place_name_view); - placeLocationView = (TextView) findViewById(R.id.place_location_view); - placeAddressView = (TextView) findViewById(R.id.place_address_view); - - reactiveLocationProvider = new ReactiveLocationProvider(this); - - getPlaceIdFromIntent(); - } - - private void getPlaceIdFromIntent() { - Intent loadedIntent = getIntent(); - placeId = loadedIntent.getStringExtra(EXTRA_PLACE_ID); - - if (placeId == null) { - throw new IllegalStateException("You must start SearchResultsActivity with a non-null place Id using getStartIntent(Context, String)"); - } - } - - @Override - protected void onLocationPermissionGranted() { - compositeSubscription = new CompositeDisposable(); - compositeSubscription.add(reactiveLocationProvider.getPlaceById(placeId) - .subscribe(new Consumer() { - @Override - public void accept(PlaceBuffer buffer) { - Place place = buffer.get(0); - if (place != null) { - placeNameView.setText(place.getName()); - placeLocationView.setText(place.getLatLng().latitude + ", " + place.getLatLng().longitude); - placeAddressView.setText(place.getAddress()); - } - buffer.release(); - } - })); - } - - @Override - protected void onStop() { - super.onStop(); - dispose(compositeSubscription); - } -} diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt new file mode 100755 index 00000000..1e6bbf74 --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.kt @@ -0,0 +1,63 @@ +package pl.charmas.android.reactivelocation2.sample + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.widget.TextView +import io.reactivex.disposables.CompositeDisposable +import pl.charmas.android.reactivelocation2.ReactiveLocationProvider +import pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent + +class PlacesResultActivity : BaseActivity() { + private lateinit var reactiveLocationProvider: ReactiveLocationProvider + private lateinit var compositeSubscription: CompositeDisposable + private lateinit var placeNameView: TextView + private lateinit var placeLocationView: TextView + private lateinit var placeAddressView: TextView + private lateinit var placeId: String + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_places_result) + placeNameView = findViewById(R.id.place_name_view) as TextView + placeLocationView = findViewById(R.id.place_location_view) as TextView + placeAddressView = findViewById(R.id.place_address_view) as TextView + reactiveLocationProvider = ReactiveLocationProvider(this, getString(R.string.API_KEY)) + placeIdFromIntent + } + + private val placeIdFromIntent: Unit + get() { + val loadedIntent = intent + placeId = loadedIntent.getStringExtra(EXTRA_PLACE_ID) + ?: throw IllegalArgumentException("You must start SearchResultsActivity with a non-null place Id using getStartIntent(Context, String)") + } + + override fun onLocationPermissionGranted() { + compositeSubscription = CompositeDisposable() + compositeSubscription.add( + reactiveLocationProvider.getPlaceById(placeId) + .subscribe { res -> + val place = res.place + placeNameView.text = place.name + val text = place.latLng?.latitude.toString() + ", " + place.latLng?.longitude + placeLocationView.text = text + placeAddressView.text = place.address + } + ) + } + + override fun onStop() { + super.onStop() + UnsubscribeIfPresent.dispose(compositeSubscription) + } + + companion object { + private const val EXTRA_PLACE_ID = "EXTRA_PLACE_ID" + fun getStartIntent(context: Context?, placeId: String): Intent { + val startIntent = Intent(context, PlacesResultActivity::class.java) + startIntent.putExtra(EXTRA_PLACE_ID, placeId) + return startIntent + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/AddressExt.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/AddressExt.kt new file mode 100755 index 00000000..782796c0 --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/AddressExt.kt @@ -0,0 +1,14 @@ +package pl.charmas.android.reactivelocation2.sample.ext + +import android.location.Address + +fun Address.addressToString(): String { + var addressLines = "" + for (i in 0..maxAddressLineIndex) { + addressLines += """ + ${getAddressLine(i)} + + """.trimIndent() + } + return addressLines +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/ContextExt.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/ContextExt.kt new file mode 100644 index 00000000..a53d5f46 --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/ContextExt.kt @@ -0,0 +1,14 @@ +package pl.charmas.android.reactivelocation2.sample.ext + +import android.content.Context +import android.widget.Toast + +fun Context.toast( + text: String +) { + Toast.makeText( + this, + text, + Toast.LENGTH_SHORT + ).show() +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/DisposableExt.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/DisposableExt.kt new file mode 100644 index 00000000..11cdc6b7 --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/DisposableExt.kt @@ -0,0 +1,8 @@ +package pl.charmas.android.reactivelocation2.sample.ext + +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable + +fun Disposable.addTo(compositeDisposable: CompositeDisposable) { + compositeDisposable.add(this) +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/LocationExt.kt b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/LocationExt.kt new file mode 100755 index 00000000..3c89d779 --- /dev/null +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/ext/LocationExt.kt @@ -0,0 +1,8 @@ +package pl.charmas.android.reactivelocation2.sample.ext + +import android.location.Location +import io.reactivex.functions.Function + +fun Location.text(): String { + return "$latitude $longitude ($provider $accuracy)" +} \ No newline at end of file diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/AddressToStringFunc.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/AddressToStringFunc.java deleted file mode 100755 index c0bcc913..00000000 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/AddressToStringFunc.java +++ /dev/null @@ -1,18 +0,0 @@ -package pl.charmas.android.reactivelocation2.sample.utils; - -import android.location.Address; - -import io.reactivex.functions.Function; - -public class AddressToStringFunc implements Function { - @Override - public String apply(Address address) { - if (address == null) return ""; - - String addressLines = ""; - for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) { - addressLines += address.getAddressLine(i) + '\n'; - } - return addressLines; - } -} diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/LocationToStringFunc.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/LocationToStringFunc.java deleted file mode 100755 index dc9e9827..00000000 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/LocationToStringFunc.java +++ /dev/null @@ -1,14 +0,0 @@ -package pl.charmas.android.reactivelocation2.sample.utils; - -import android.location.Location; - -import io.reactivex.functions.Function; - -public class LocationToStringFunc implements Function { - @Override - public String apply(Location location) { - if (location != null) - return location.getLatitude() + " " + location.getLongitude() + " (" + location.getAccuracy() + ")"; - return "no location available"; - } -} diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/RxTextView.java b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/RxTextView.java index 7c9ead44..2c2e5553 100644 --- a/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/RxTextView.java +++ b/sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/RxTextView.java @@ -1,9 +1,8 @@ package pl.charmas.android.reactivelocation2.sample.utils; -import android.support.annotation.CheckResult; -import android.support.annotation.NonNull; import android.widget.TextView; - +import androidx.annotation.CheckResult; +import androidx.annotation.NonNull; import io.reactivex.Observable; diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml index c4942281..1b8e4647 100644 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -22,6 +22,58 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:text="Sample location" /> + + + + + + + + + + + +