├── .gitignore ├── CHANGELOG.md ├── README.md ├── android-reactive-location ├── .gitignore ├── build.gradle ├── gradle.properties └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── pl │ └── charmas │ └── android │ └── reactivelocation2 │ ├── DataBufferObservable.java │ ├── ReactiveLocationProvider.java │ ├── ReactiveLocationProviderConfiguration.java │ └── observables │ ├── BaseLocationObservableOnSubscribe.java │ ├── BaseObservableOnSubscribe.java │ ├── GoogleAPIClientObservableOnSubscribe.java │ ├── GoogleAPIConnectionException.java │ ├── GoogleAPIConnectionSuspendedException.java │ ├── ObservableContext.java │ ├── ObservableEmitterWrapper.java │ ├── ObservableFactory.java │ ├── PendingResultObservableOnSubscribe.java │ ├── StatusException.java │ ├── activity │ ├── ActivityUpdatesObservableOnSubscribe.java │ └── BaseActivityObservableOnSubscribe.java │ ├── geocode │ ├── FallbackReverseGeocodeObservable.java │ ├── GeocodeObservable.java │ └── ReverseGeocodeObservable.java │ ├── geofence │ ├── AddGeofenceObservableOnSubscribe.java │ ├── RemoveGeofenceByPendingIntentObservableOnSubscribe.java │ ├── RemoveGeofenceObservableOnSubscribe.java │ └── RemoveGeofenceRequestIdsObservableOnSubscribe.java │ └── location │ ├── AddLocationIntentUpdatesObservableOnSubscribe.java │ ├── LastKnownLocationObservableOnSubscribe.java │ ├── LocationUpdatesObservableOnSubscribe.java │ ├── MockLocationObservableOnSubscribe.java │ └── RemoveLocationIntentUpdatesObservableOnSubscribe.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── maven_push.gradle ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.txt └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── pl │ │ └── charmas │ │ └── android │ │ └── reactivelocation2 │ │ └── sample │ │ ├── BaseActivity.java │ │ ├── GeofenceActivity.java │ │ ├── GeofenceBroadcastReceiver.java │ │ ├── MainActivity.java │ │ ├── MockLocationsActivity.java │ │ ├── PlacesActivity.java │ │ ├── PlacesResultActivity.java │ │ └── utils │ │ ├── AddressToStringFunc.java │ │ ├── DetectedActivityToString.java │ │ ├── DisplayTextOnViewAction.java │ │ ├── LocationToStringFunc.java │ │ ├── RxTextView.java │ │ ├── TextViewTextOnSubscribe.java │ │ ├── ToMostProbableActivity.java │ │ └── UnsubscribeIfPresent.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ ├── activity_geofence.xml │ ├── activity_main.xml │ ├── activity_mocklocations.xml │ ├── activity_places.xml │ └── activity_places_result.xml │ └── values │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /.idea/ 3 | /local.properties 4 | /.idea 5 | build/ 6 | .DS_Store 7 | *.iml 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | Version 2.1 5 | ---------------------------- 6 | * Fix issues with emitting values when observable is disposed 7 | * Add option to auto reconnect to GMS when connection is suspended 8 | 9 | Version 2.0 10 | ---------------------------- 11 | * Support for RxJava2 12 | 13 | Version 1.0 14 | ---------------------------- 15 | * Add Locale to geocode observable 16 | * Added option to pass GMS callback Handler while creating ReactiveLocationProvider 17 | * Bugfix: crash on unregistering activity BroadcastReceiver 18 | * Fixed memory leak in location observable 19 | * Checking if subscripion is still on before emitting values in few places 20 | 21 | Version 0.10 22 | ---------------------------- 23 | * Updated dependencies 24 | * Added permission annotations 25 | * Minor improvements and cleanups in sample app 26 | 27 | Version 0.8.1 28 | ---------------------------- 29 | * Updated dependencies 30 | * Added fallback reverse geocode observable that uses web apis to obtain address 31 | * Added API to pass locales for reverse geocoding 32 | 33 | Version 0.8 34 | ---------------------------- 35 | * Updated dependencies to Google Play Services 8.1 36 | 37 | Version 0.7 38 | ---------------------------- 39 | 40 | * Removed ```final``` from methods in ```ReactiveLocationProvider``` to enable mockito mocking. 41 | * Updated dependencies. 42 | * Added support to fetch Place by id. 43 | 44 | Version 0.6 45 | ---------------------------- 46 | 47 | * Added support for mock locations. 48 | * Corrected geocode to reverse geocode and proper geocode implemented. 49 | * Added support for PendingIntent location updates. 50 | * Simplified and unified observables that were based on status now return status instead of custom responses. 51 | * Updated to newest Play Services version. 52 | 53 | Version 0.5 54 | ---------------------------- 55 | 56 | * Fix: now last known location observable when location is disabled emits nothing and completes (instead of null). 57 | * Added Places API support from Google Play Services 7.0.0 58 | * Added Location Settings API from Google Play Services 7.0.0 59 | * Added support for obtaining connection to Google Play Services though observable. 60 | * Added utils to handle ```PendingResponse``` and ```Buffers``` from Google Play Services 61 | 62 | Version 0.4 63 | ---------------------------- 64 | 65 | Sorry - no changelog history here. 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ReactiveLocation library for Android 2 | ==================================== 3 | 4 | Small library that wraps Google Play Services API in brilliant [RxJava](https://github.com/ReactiveX/RxJava) 5 | ```Observables``` reducing boilerplate to minimum. 6 | 7 | Current stable version - 2.1 8 | --------------- 9 | 10 | **This version works with Google Play Services 11+ and RxJava 2.+** 11 | 12 | Artifact name: ```android-reactive-location2``` 13 | 14 | RxJava1 stable version - 1.0 15 | -------------- 16 | **RxJava1 version:** 17 | 18 | Artifact name: ```android-reactive-location``` 19 | 20 | What can you do with that? 21 | -------------------------- 22 | 23 | * easily connect to Play Services API 24 | * obtain last known location 25 | * subscribe for location updates 26 | * use location settings API 27 | * manage geofences 28 | * geocode location to list of addresses 29 | * activity recognition 30 | * use current place API 31 | * fetch place autocomplete suggestions 32 | 33 | How does the API look like? 34 | ---------------------------- 35 | 36 | Simple. All you need is to create ```ReactiveLocationProvider``` using your context. 37 | All observables are already there. Examples are worth more than 1000 words: 38 | 39 | 40 | ### Getting last known location 41 | 42 | ```java 43 | ReactiveLocationProvider locationProvider = new ReactiveLocationProvider(context); 44 | locationProvider.getLastKnownLocation() 45 | .subscribe(new Consumer() { 46 | @Override 47 | public void call(Location location) { 48 | doSthImportantWithObtainedLocation(location); 49 | } 50 | }); 51 | ``` 52 | 53 | Yep, Java 8 is not there yet (and on Android it will take a while) but there is 54 | absolutely no Google Play Services LocationClient callbacks hell and there is no 55 | clean-up you have to do. 56 | 57 | ### Subscribing for location updates 58 | 59 | ```java 60 | LocationRequest request = LocationRequest.create() //standard GMS LocationRequest 61 | .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) 62 | .setNumUpdates(5) 63 | .setInterval(100); 64 | 65 | ReactiveLocationProvider locationProvider = new ReactiveLocationProvider(context); 66 | Subscription subscription = locationProvider.getUpdatedLocation(request) 67 | .filter(...) // you can filter location updates 68 | .map(...) // you can map location to sth different 69 | .flatMap(...) // or event flat map 70 | ... // and do everything else that is provided by RxJava 71 | .subscribe(new Consumer() { 72 | @Override 73 | public void call(Location location) { 74 | doSthImportantWithObtainedLocation(location); 75 | } 76 | }); 77 | ``` 78 | 79 | When you are done (for example in ```onStop()```) remember to unsubscribe. 80 | 81 | ```java 82 | subscription.unsubscribe(); 83 | ``` 84 | 85 | ### Subscribing for Activity Recognition 86 | 87 | Getting activity recognition is just as simple 88 | 89 | ```java 90 | 91 | ReactiveLocationProvider locationProvider = new ReactiveLocationProvider(context); 92 | Subscription subscription = locationProvider.getDetectedActivity(0) // detectionIntervalMillis 93 | .filter(...) // you can filter location updates 94 | .map(...) // you can map location to sth different 95 | .flatMap(...) // or event flat map 96 | ... // and do everything else that is provided by RxJava 97 | .subscribe(new Consumer() { 98 | @Override 99 | public void call(ActivityRecognitionResult detectedActivity) { 100 | doSthImportantWithObtainedActivity(detectedActivity); 101 | } 102 | }); 103 | ``` 104 | 105 | ### Reverse geocode location 106 | 107 | Do you need address for location? 108 | 109 | ```java 110 | Observable> reverseGeocodeObservable = locationProvider 111 | .getReverseGeocodeObservable(location.getLatitude(), location.getLongitude(), MAX_ADDRESSES); 112 | 113 | reverseGeocodeObservable 114 | .subscribeOn(Schedulers.io()) // use I/O thread to query for addresses 115 | .observeOn(AndroidSchedulers.mainThread()) // return result in main android thread to manipulate UI 116 | .subscribe(...); 117 | ``` 118 | 119 | ### Geocode location 120 | 121 | Do you need address for a text search query? 122 | 123 | ```java 124 | Observable> geocodeObservable = locationProvider 125 | .getGeocodeObservable(String userQuery, MAX_ADDRESSES); 126 | 127 | geocodeObservable 128 | .subscribeOn(Schedulers.io()) 129 | .observeOn(AndroidSchedulers.mainThread()) 130 | .subscribe(...); 131 | ``` 132 | 133 | ### Managing geofences 134 | 135 | For geofence management use `addGeofences` and `removeGeofences` methods. 136 | 137 | ### Checking location settings though location settings API 138 | 139 | To get ```LocationSettingsResponse``` for your ```LocationRequest``` check 140 | out ```ReactiveLocationProvider.checkLocationSettings()``` method. Sample 141 | usage can be found in **sample** project in ```MainActivity``` class. 142 | 143 | ### Connecting to Google Play Services API 144 | 145 | If you just need managed connection to Play Services API 146 | use ```ReactiveLocationProvider.getGoogleApiClientObservable()```. 147 | On subscription it will connect to the API. 148 | Unsubscription will close the connection. 149 | 150 | ### Creating observable from PendingResult 151 | 152 | If you are manually using Google Play Services and you are dealing with 153 | ```PendingResult``` you can easily transform them to observables with 154 | ```ReactiveLocationProvider.fromPendingResult()``` method. 155 | 156 | ### Transforming buffers to observable 157 | 158 | To transform any buffer to observable and autorelease it on unsubscription 159 | use ```DataBufferObservable.from()``` method. It will let you easily flatMap 160 | such data as ```PlaceLikelihoodBuffer``` or ```AutocompletePredictionBuffer``` 161 | from Places API. For usage example see ```PlacesActivity``` sample. 162 | 163 | ### Places API 164 | 165 | You can fetch current place or place suggestions using: 166 | 167 | * ```ReactiveLocationProvider.getCurrentPlace()``` 168 | * ```ReactiveLocationProvider.getPlaceAutocompletePredictions()``` 169 | * ```ReactiveLocationProvider.getPlaceById()``` 170 | 171 | For more info see sample project and ```PlacesActivity```. 172 | 173 | ### Cooler examples 174 | 175 | Do you need location with certain accuracy but don't want to wait for it more than 4 sec? No problem. 176 | 177 | ```java 178 | LocationRequest req = LocationRequest.create() 179 | .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) 180 | .setExpirationDuration(TimeUnit.SECONDS.toMillis(LOCATION_TIMEOUT_IN_SECONDS)) 181 | .setInterval(LOCATION_UPDATE_INTERVAL); 182 | 183 | Observable goodEnoughQuicklyOrNothingObservable = locationProvider.getUpdatedLocation(req) 184 | .filter(new Func1() { 185 | @Override 186 | public Boolean call(Location location) { 187 | return location.getAccuracy() < SUFFICIENT_ACCURACY; 188 | } 189 | }) 190 | .timeout(LOCATION_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS, Observable.just((Location) null), AndroidSchedulers.mainThread()) 191 | .first() 192 | .observeOn(AndroidSchedulers.mainThread()); 193 | 194 | goodEnoughQuicklyOrNothingObservable.subscribe(...); 195 | ``` 196 | 197 | 198 | How to use it? 199 | -------------- 200 | 201 | Library is available in maven central. 202 | 203 | ### Gradle 204 | 205 | Just use it as dependency in your *build.gradle* file 206 | along with Google Play Services and RxJava. 207 | 208 | ```groovy 209 | dependencies { 210 | ... 211 | compile 'pl.charmas.android:android-reactive-location2:2.1@aar' 212 | compile 'com.google.android.gms:play-services-location:11.0.4' //you can use newer GMS version if you need 213 | compile 'com.google.android.gms:play-services-places:11.0.4' 214 | compile 'io.reactivex:rxjava:2.0.5' //you can override RxJava version if you need 215 | } 216 | ``` 217 | 218 | ### Maven 219 | 220 | Ensure you have android-maven-plugin version that support **aar** archives and add 221 | following dependency: 222 | 223 | ```xml 224 | 225 | pl.charmas.android 226 | android-reactive-location2 227 | 2.1 228 | aar 229 | 230 | ``` 231 | 232 | It may be necessary to add google play services and rxanroid dependency as well. 233 | 234 | Sample 235 | ------ 236 | 237 | Sample usage is available in *sample* directory. 238 | 239 | Places API requires API Key. Before running samples you need to create project on API console 240 | and obtain API Key using this [guide](https://developers.google.com/places/android/signup). 241 | Obtained key should be exported as gradle property named: ```REACTIVE_LOCATION_GMS_API_KEY``` for 242 | example in ```~/.gradle/gradle.properties```. 243 | 244 | 245 | References 246 | ------ 247 | 248 | If you need Google Fit library rxified please take a look at [RxFit](https://github.com/patloew/RxFit). 249 | 250 | License 251 | ======= 252 | 253 | Copyright (C) 2015 Michał Charmas (http://blog.charmas.pl) 254 | 255 | Licensed under the Apache License, Version 2.0 (the "License"); 256 | you may not use this file except in compliance with the License. 257 | You may obtain a copy of the License at 258 | 259 | http://www.apache.org/licenses/LICENSE-2.0 260 | 261 | Unless required by applicable law or agreed to in writing, software 262 | distributed under the License is distributed on an "AS IS" BASIS, 263 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 264 | See the License for the specific language governing permissions and 265 | limitations under the License. 266 | -------------------------------------------------------------------------------- /android-reactive-location/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /android-reactive-location/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'maven' 3 | 4 | android { 5 | compileSdkVersion rootProject.ext.compileSdkVersion 6 | buildToolsVersion rootProject.ext.buildToolsVersion 7 | 8 | defaultConfig { 9 | minSdkVersion 14 10 | targetSdkVersion rootProject.ext.targetSdkVersion 11 | } 12 | 13 | compileOptions { 14 | sourceCompatibility JavaVersion.VERSION_1_7 15 | targetCompatibility JavaVersion.VERSION_1_7 16 | } 17 | } 18 | 19 | //TODO: local maven deployment 20 | 21 | dependencies { 22 | compile 'com.google.android.gms:play-services-location:11.0.4' 23 | compile 'com.google.android.gms:play-services-places:11.0.4' 24 | compile 'io.reactivex.rxjava2:rxjava:2.0.5' 25 | } 26 | 27 | // Comment this to deploy to local maven repository 28 | apply from: '../maven_push.gradle' 29 | 30 | 31 | //TODO: clean up 32 | //This is for local maven deployment 33 | 34 | //apply plugin: 'maven' 35 | //uploadArchives { 36 | // repositories { 37 | // mavenDeployer { 38 | // repository url: 'file://' + new File(System.getProperty('user.home'), '.m2/repository').absolutePath 39 | // } 40 | // } 41 | //} 42 | //task install(dependsOn: uploadArchives) 43 | -------------------------------------------------------------------------------- /android-reactive-location/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=Android ReactiveLocation 2 | POM_ARTIFACT_ID=android-reactive-location2 3 | POM_PACKAGING=aar 4 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/DataBufferObservable.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2; 2 | 3 | import com.google.android.gms.common.data.AbstractDataBuffer; 4 | 5 | import io.reactivex.Observable; 6 | import io.reactivex.ObservableEmitter; 7 | import io.reactivex.ObservableOnSubscribe; 8 | import io.reactivex.disposables.Disposables; 9 | import io.reactivex.functions.Action; 10 | 11 | 12 | /** 13 | * Util class that creates observable from buffer. 14 | */ 15 | public final class DataBufferObservable { 16 | 17 | private DataBufferObservable() { 18 | //no instance 19 | } 20 | 21 | /** 22 | * Creates observable from buffer. On unsubscribe buffer is automatically released. 23 | * 24 | * @param buffer source buffer 25 | * @param item type 26 | * @return observable that emits all items from buffer and on unsubscription releases it 27 | */ 28 | public static Observable from(final AbstractDataBuffer buffer) { 29 | return Observable.create(new ObservableOnSubscribe() { 30 | 31 | @Override 32 | public void subscribe(final ObservableEmitter emitter) { 33 | for (T item : buffer) { 34 | emitter.onNext(item); 35 | } 36 | emitter.setDisposable(Disposables.fromAction(new Action() { 37 | @Override 38 | public void run() throws Exception { 39 | buffer.release(); 40 | } 41 | })); 42 | } 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProvider.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2; 2 | 3 | import android.app.PendingIntent; 4 | import android.content.Context; 5 | import android.location.Address; 6 | import android.location.Location; 7 | import android.os.Handler; 8 | import android.support.annotation.Nullable; 9 | import android.support.annotation.RequiresPermission; 10 | 11 | import com.google.android.gms.common.api.Api; 12 | import com.google.android.gms.common.api.GoogleApiClient; 13 | import com.google.android.gms.common.api.PendingResult; 14 | import com.google.android.gms.common.api.Result; 15 | import com.google.android.gms.common.api.Status; 16 | import com.google.android.gms.location.ActivityRecognitionResult; 17 | import com.google.android.gms.location.GeofencingRequest; 18 | import com.google.android.gms.location.LocationRequest; 19 | import com.google.android.gms.location.LocationServices; 20 | import com.google.android.gms.location.LocationSettingsRequest; 21 | import com.google.android.gms.location.LocationSettingsResult; 22 | import com.google.android.gms.location.places.AutocompleteFilter; 23 | import com.google.android.gms.location.places.AutocompletePredictionBuffer; 24 | import com.google.android.gms.location.places.PlaceBuffer; 25 | import com.google.android.gms.location.places.PlaceFilter; 26 | import com.google.android.gms.location.places.PlaceLikelihoodBuffer; 27 | import com.google.android.gms.location.places.PlacePhotoMetadata; 28 | import com.google.android.gms.location.places.PlacePhotoMetadataResult; 29 | import com.google.android.gms.location.places.PlacePhotoResult; 30 | import com.google.android.gms.location.places.Places; 31 | import com.google.android.gms.maps.model.LatLngBounds; 32 | 33 | import java.util.List; 34 | import java.util.Locale; 35 | 36 | import io.reactivex.Observable; 37 | import io.reactivex.functions.Function; 38 | import pl.charmas.android.reactivelocation2.observables.GoogleAPIClientObservableOnSubscribe; 39 | import pl.charmas.android.reactivelocation2.observables.ObservableContext; 40 | import pl.charmas.android.reactivelocation2.observables.ObservableFactory; 41 | import pl.charmas.android.reactivelocation2.observables.PendingResultObservableOnSubscribe; 42 | import pl.charmas.android.reactivelocation2.observables.activity.ActivityUpdatesObservableOnSubscribe; 43 | import pl.charmas.android.reactivelocation2.observables.geocode.GeocodeObservable; 44 | import pl.charmas.android.reactivelocation2.observables.geocode.ReverseGeocodeObservable; 45 | import pl.charmas.android.reactivelocation2.observables.geofence.AddGeofenceObservableOnSubscribe; 46 | import pl.charmas.android.reactivelocation2.observables.geofence.RemoveGeofenceObservableOnSubscribe; 47 | import pl.charmas.android.reactivelocation2.observables.location.AddLocationIntentUpdatesObservableOnSubscribe; 48 | import pl.charmas.android.reactivelocation2.observables.location.LastKnownLocationObservableOnSubscribe; 49 | import pl.charmas.android.reactivelocation2.observables.location.LocationUpdatesObservableOnSubscribe; 50 | import pl.charmas.android.reactivelocation2.observables.location.MockLocationObservableOnSubscribe; 51 | import pl.charmas.android.reactivelocation2.observables.location.RemoveLocationIntentUpdatesObservableOnSubscribe; 52 | 53 | 54 | /** 55 | * Factory of observables that can manipulate location 56 | * delivered by Google Play Services. 57 | */ 58 | public class ReactiveLocationProvider { 59 | private final ObservableContext ctx; 60 | private final ObservableFactory factory; 61 | 62 | /** 63 | * Creates location provider instance with default configuration. 64 | * 65 | * @param ctx preferably application context 66 | */ 67 | public ReactiveLocationProvider(Context ctx) { 68 | this(ctx, ReactiveLocationProviderConfiguration.builder().build()); 69 | } 70 | 71 | /** 72 | * Create location provider with given {@link ReactiveLocationProviderConfiguration}. 73 | * 74 | * @param ctx preferably application context 75 | * @param configuration configuration instance 76 | */ 77 | public ReactiveLocationProvider(Context ctx, ReactiveLocationProviderConfiguration configuration) { 78 | this.ctx = new ObservableContext(ctx, configuration); 79 | this.factory = new ObservableFactory(this.ctx); 80 | } 81 | 82 | /** 83 | * Creates location provider with custom handler in which all GooglePlayServices callbacks are called. 84 | * 85 | * @param ctx preferably application context 86 | * @param handler on which all GooglePlayServices callbacks are called 87 | * @see com.google.android.gms.common.api.GoogleApiClient.Builder#setHandler(android.os.Handler) 88 | * @deprecated please use {@link ReactiveLocationProvider#ReactiveLocationProvider(Context, ReactiveLocationProviderConfiguration)} 89 | */ 90 | @Deprecated 91 | public ReactiveLocationProvider(Context ctx, Handler handler) { 92 | this(ctx, ReactiveLocationProviderConfiguration.builder().setCustomCallbackHandler(handler).build()); 93 | } 94 | 95 | /** 96 | * Creates observable that obtains last known location and than completes. 97 | * Delivered location is never null - when it is unavailable Observable completes without emitting 98 | * any value. 99 | *

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

119 | * Observable can report {@link pl.charmas.android.reactivelocation2.observables.GoogleAPIConnectionException} 120 | * when there are trouble connecting with Google Play Services and other exceptions that 121 | * 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)}. 122 | * Everything is delivered by {@link io.reactivex.Observer#onError(Throwable)}. 123 | * 124 | * @param locationRequest request object with info about what kind of location you need 125 | * @return observable that serves infinite stream of location updates 126 | */ 127 | @RequiresPermission( 128 | anyOf = {"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"} 129 | ) 130 | public Observable getUpdatedLocation(LocationRequest locationRequest) { 131 | return LocationUpdatesObservableOnSubscribe.createObservable(ctx, factory, locationRequest); 132 | } 133 | 134 | /** 135 | * Returns an observable which activates mock location mode when subscribed to, using the 136 | * supplied observable as a source of mock locations. Mock locations will replace normal 137 | * location information for all users of the FusedLocationProvider API on the device while this 138 | * observable is subscribed to. 139 | *

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

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

146 | * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. 147 | * 148 | * @param sourceLocationObservable observable that emits {@link android.location.Location} instances suitable to use as mock locations 149 | * @return observable that emits {@link com.google.android.gms.common.api.Status} 150 | */ 151 | @RequiresPermission( 152 | allOf = {"android.permission.ACCESS_COARSE_LOCATION", 153 | "android.permission.ACCESS_MOCK_LOCATION"} 154 | ) 155 | public Observable mockLocation(Observable sourceLocationObservable) { 156 | return MockLocationObservableOnSubscribe.createObservable(ctx, factory, sourceLocationObservable); 157 | } 158 | 159 | /** 160 | * Creates an observable that adds a {@link android.app.PendingIntent} as a location listener. 161 | *

162 | * 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)}. 163 | *

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

167 | * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. 168 | * 169 | * @param locationRequest request object with info about what kind of location you need 170 | * @param intent PendingIntent that will be called with location updates 171 | * @return observable that adds the request and PendingIntent 172 | */ 173 | @RequiresPermission( 174 | anyOf = {"android.permission.ACCESS_COARSE_LOCATION", "android.permission.ACCESS_FINE_LOCATION"} 175 | ) 176 | public Observable requestLocationUpdates(LocationRequest locationRequest, PendingIntent intent) { 177 | return AddLocationIntentUpdatesObservableOnSubscribe.createObservable(ctx, factory, locationRequest, intent); 178 | } 179 | 180 | /** 181 | * Observable that can be used to remove {@link android.app.PendingIntent} location updates. 182 | *

183 | * In case of unsuccessful status {@link pl.charmas.android.reactivelocation2.observables.StatusException} is delivered. 184 | * 185 | * @param intent PendingIntent to remove location updates for 186 | * @return observable that removes the PendingIntent 187 | */ 188 | public Observable removeLocationUpdates(PendingIntent intent) { 189 | return RemoveLocationIntentUpdatesObservableOnSubscribe.createObservable(ctx, factory, intent); 190 | } 191 | 192 | /** 193 | * Creates observable that translates latitude and longitude to list of possible addresses using 194 | * included Geocoder class. In case geocoder fails with IOException("Service not Available") fallback 195 | * decoder is used using google web api. You should subscribe for this observable on I/O thread. 196 | * The stream finishes after address list is available. 197 | * 198 | * @param lat latitude 199 | * @param lng longitude 200 | * @param maxResults maximal number of results you are interested in 201 | * @return observable that serves list of address based on location 202 | */ 203 | public Observable> getReverseGeocodeObservable(double lat, double lng, int maxResults) { 204 | return ReverseGeocodeObservable.createObservable(ctx.getContext(), factory, Locale.getDefault(), lat, lng, maxResults); 205 | } 206 | 207 | /** 208 | * Creates observable that translates latitude and longitude to list of possible addresses using 209 | * included Geocoder class. In case geocoder fails with IOException("Service not Available") fallback 210 | * decoder is used using google web api. You should subscribe for this observable on I/O thread. 211 | * The stream finishes after address list is available. 212 | * 213 | * @param locale locale for address language 214 | * @param lat latitude 215 | * @param lng longitude 216 | * @param maxResults maximal number of results you are interested in 217 | * @return observable that serves list of address based on location 218 | */ 219 | public Observable> getReverseGeocodeObservable(Locale locale, double lat, double lng, int maxResults) { 220 | return ReverseGeocodeObservable.createObservable(ctx.getContext(), factory, locale, lat, lng, maxResults); 221 | } 222 | 223 | /** 224 | * Creates observable that translates a street address or other description into a list of 225 | * possible addresses using included Geocoder class. You should subscribe for this 226 | * observable on I/O thread. 227 | * The stream finishes after address list is available. 228 | * 229 | * @param locationName a user-supplied description of a location 230 | * @param maxResults max number of results you are interested in 231 | * @return observable that serves list of address based on location name 232 | */ 233 | public Observable> getGeocodeObservable(String locationName, int maxResults) { 234 | return getGeocodeObservable(locationName, maxResults, null); 235 | } 236 | 237 | /** 238 | * Creates geocoder with default Locale. 239 | * 240 | * @see ReactiveLocationProvider#getGeocodeObservable(String, int, LatLngBounds, Locale) 241 | */ 242 | public Observable> getGeocodeObservable(String locationName, int maxResults, LatLngBounds bounds) { 243 | return getGeocodeObservable(locationName, maxResults, bounds, null); 244 | } 245 | 246 | /** 247 | * Creates observable that translates a street address or other description into a list of 248 | * possible addresses using included Geocoder class. You should subscribe for this 249 | * observable on I/O thread. 250 | * The stream finishes after address list is available. 251 | *

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

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

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

272 | * 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)} 273 | * 274 | * @param geofenceTransitionPendingIntent pending intent to register on geofence transition 275 | * @param request list of request to add 276 | * @return observable that adds request 277 | */ 278 | @RequiresPermission("android.permission.ACCESS_FINE_LOCATION") 279 | public Observable addGeofences(PendingIntent geofenceTransitionPendingIntent, GeofencingRequest request) { 280 | return AddGeofenceObservableOnSubscribe.createObservable(ctx, factory, request, geofenceTransitionPendingIntent); 281 | } 282 | 283 | /** 284 | * Observable that can be used to remove geofences from LocationClient. 285 | *

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

288 | * 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)}. 289 | *

290 | * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. 291 | * 292 | * @param pendingIntent key of registered geofences 293 | * @return observable that removed geofences 294 | */ 295 | public Observable removeGeofences(PendingIntent pendingIntent) { 296 | return RemoveGeofenceObservableOnSubscribe.createObservable(ctx, factory, pendingIntent); 297 | } 298 | 299 | /** 300 | * Observable that can be used to remove geofences from LocationClient. 301 | *

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

304 | * 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)}. 305 | *

306 | * Every exception is delivered by {@link io.reactivex.Observer#onError(Throwable)}. 307 | * 308 | * @param requestIds geofences to remove 309 | * @return observable that removed geofences 310 | */ 311 | public Observable removeGeofences(List requestIds) { 312 | return RemoveGeofenceObservableOnSubscribe.createObservable(ctx, factory, requestIds); 313 | } 314 | 315 | 316 | /** 317 | * Observable that can be used to observe activity provided by Actity Recognition mechanism. 318 | * 319 | * @param detectIntervalMiliseconds detecion interval 320 | * @return observable that provides activity recognition 321 | */ 322 | public Observable getDetectedActivity(int detectIntervalMiliseconds) { 323 | return ActivityUpdatesObservableOnSubscribe.createObservable(ctx, factory, detectIntervalMiliseconds); 324 | } 325 | 326 | /** 327 | * Observable that can be used to check settings state for given location request. 328 | * 329 | * @param locationRequest location request 330 | * @return observable that emits check result of location settings 331 | * @see com.google.android.gms.location.SettingsApi 332 | */ 333 | public Observable checkLocationSettings(final LocationSettingsRequest locationRequest) { 334 | return getGoogleApiClientObservable(LocationServices.API) 335 | .flatMap(new Function>() { 336 | @Override 337 | public Observable apply(GoogleApiClient googleApiClient) { 338 | return fromPendingResult(LocationServices.SettingsApi.checkLocationSettings(googleApiClient, locationRequest)); 339 | } 340 | }); 341 | } 342 | 343 | /** 344 | * Returns observable that fetches current place from Places API. To flatmap and auto release 345 | * buffer to {@link com.google.android.gms.location.places.PlaceLikelihood} observable use 346 | * {@link DataBufferObservable}. 347 | * 348 | * @param placeFilter filter 349 | * @return observable that emits current places buffer and completes 350 | */ 351 | public Observable getCurrentPlace(@Nullable final PlaceFilter placeFilter) { 352 | return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) 353 | .flatMap(new Function>() { 354 | @Override 355 | public Observable apply(GoogleApiClient api) { 356 | return fromPendingResult(Places.PlaceDetectionApi.getCurrentPlace(api, placeFilter)); 357 | } 358 | }); 359 | } 360 | 361 | /** 362 | * Returns observable that fetches a place from the Places API using the place ID. 363 | * 364 | * @param placeId id for place 365 | * @return observable that emits places buffer and completes 366 | */ 367 | public Observable getPlaceById(@Nullable final String placeId) { 368 | return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) 369 | .flatMap(new Function>() { 370 | @Override 371 | public Observable apply(GoogleApiClient api) { 372 | return fromPendingResult(Places.GeoDataApi.getPlaceById(api, placeId)); 373 | } 374 | }); 375 | } 376 | 377 | /** 378 | * Returns observable that fetches autocomplete predictions from Places API. To flatmap and autorelease 379 | * {@link com.google.android.gms.location.places.AutocompletePredictionBuffer} you can use 380 | * {@link DataBufferObservable}. 381 | * 382 | * @param query search query 383 | * @param bounds bounds where to fetch suggestions from 384 | * @param filter filter 385 | * @return observable with suggestions buffer and completes 386 | */ 387 | public Observable getPlaceAutocompletePredictions(final String query, final LatLngBounds bounds, final AutocompleteFilter filter) { 388 | return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) 389 | .flatMap(new Function>() { 390 | @Override 391 | public Observable apply(GoogleApiClient api) { 392 | return fromPendingResult(Places.GeoDataApi.getAutocompletePredictions(api, query, bounds, filter)); 393 | } 394 | }); 395 | } 396 | 397 | /** 398 | * Returns observable that fetches photo metadata from the Places API using the place ID. 399 | * 400 | * @param placeId id for place 401 | * @return observable that emits metadata buffer and completes 402 | */ 403 | public Observable getPhotoMetadataById(final String placeId) { 404 | return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) 405 | .flatMap(new Function>() { 406 | @Override 407 | public Observable apply(GoogleApiClient api) { 408 | return fromPendingResult(Places.GeoDataApi.getPlacePhotos(api, placeId)); 409 | } 410 | }); 411 | } 412 | 413 | /** 414 | * Returns observable that fetches a placePhotoMetadata from the Places API using the place placePhotoMetadata metadata. 415 | * Use after fetching the place placePhotoMetadata metadata with {@link ReactiveLocationProvider#getPhotoMetadataById(String)} 416 | * 417 | * @param placePhotoMetadata the place photo meta data 418 | * @return observable that emits the photo result and completes 419 | */ 420 | public Observable getPhotoForMetadata(final PlacePhotoMetadata placePhotoMetadata) { 421 | return getGoogleApiClientObservable(Places.PLACE_DETECTION_API, Places.GEO_DATA_API) 422 | .flatMap(new Function>() { 423 | @Override 424 | public Observable apply(GoogleApiClient api) { 425 | return fromPendingResult(placePhotoMetadata.getPhoto(api)); 426 | } 427 | }); 428 | } 429 | 430 | /** 431 | * Observable that emits {@link com.google.android.gms.common.api.GoogleApiClient} object after connection. 432 | * In case of error {@link pl.charmas.android.reactivelocation2.observables.GoogleAPIConnectionException} is emmited. 433 | * When connection to Google Play Services is suspended {@link pl.charmas.android.reactivelocation2.observables.GoogleAPIConnectionSuspendedException} 434 | * is emitted as error. 435 | * Do not disconnect from apis client manually - just unsubscribe. 436 | * 437 | * @param apis collection of apis to connect to 438 | * @return observable that emits apis client after successful connection 439 | */ 440 | public Observable getGoogleApiClientObservable(Api... apis) { 441 | //noinspection unchecked 442 | return GoogleAPIClientObservableOnSubscribe.create(ctx, factory, apis); 443 | } 444 | 445 | /** 446 | * Util method that wraps {@link com.google.android.gms.common.api.PendingResult} in Observable. 447 | * 448 | * @param result pending result to wrap 449 | * @param parameter type of result 450 | * @return observable that emits pending result and completes 451 | */ 452 | public static Observable fromPendingResult(PendingResult result) { 453 | return Observable.create(new PendingResultObservableOnSubscribe<>(result)); 454 | } 455 | } 456 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/ReactiveLocationProviderConfiguration.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2; 2 | 3 | import android.os.Handler; 4 | import android.support.annotation.Nullable; 5 | 6 | /** 7 | * Configuration for location provider. Pleas use builder to create an instance. 8 | */ 9 | public class ReactiveLocationProviderConfiguration { 10 | private final Handler customCallbackHandler; 11 | private final boolean retryOnConnectionSuspended; 12 | 13 | private ReactiveLocationProviderConfiguration(Builder builder) { 14 | this.customCallbackHandler = builder.customCallbackHandler; 15 | this.retryOnConnectionSuspended = builder.retryOnConnectionSuspended; 16 | } 17 | 18 | public Handler getCustomCallbackHandler() { 19 | return customCallbackHandler; 20 | } 21 | 22 | public boolean isRetryOnConnectionSuspended() { 23 | return retryOnConnectionSuspended; 24 | } 25 | 26 | public static Builder builder() { 27 | return new Builder(); 28 | } 29 | 30 | public static class Builder { 31 | private Handler customCallbackHandler = null; 32 | private boolean retryOnConnectionSuspended = false; 33 | 34 | /** 35 | * Allows to set custom handler on which all Google Play Services callbacks are called. 36 | *

37 | * Default: null 38 | * 39 | * @param customCallbackHandler handler instance 40 | * @return builder instance 41 | */ 42 | public Builder setCustomCallbackHandler(@Nullable Handler customCallbackHandler) { 43 | this.customCallbackHandler = customCallbackHandler; 44 | return this; 45 | } 46 | 47 | /** 48 | * Property that allows automatic retries of connection to Google Play Services when it has bean suspended. 49 | *

50 | * Default: false 51 | * 52 | * @param retryOnConnectionSuspended if should we retry on connection failure 53 | * @return builder instance 54 | */ 55 | public Builder setRetryOnConnectionSuspended(boolean retryOnConnectionSuspended) { 56 | this.retryOnConnectionSuspended = retryOnConnectionSuspended; 57 | return this; 58 | } 59 | 60 | /** 61 | * Builds configuration instance 62 | * 63 | * @return configuration instance 64 | */ 65 | public ReactiveLocationProviderConfiguration build() { 66 | return new ReactiveLocationProviderConfiguration(this); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseLocationObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables; 2 | 3 | import com.google.android.gms.location.LocationServices; 4 | 5 | public abstract class BaseLocationObservableOnSubscribe extends BaseObservableOnSubscribe { 6 | protected BaseLocationObservableOnSubscribe(ObservableContext ctx) { 7 | super(ctx, LocationServices.API); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/BaseObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.support.annotation.NonNull; 7 | 8 | import com.google.android.gms.common.ConnectionResult; 9 | import com.google.android.gms.common.api.Api; 10 | import com.google.android.gms.common.api.GoogleApiClient; 11 | 12 | import java.util.Arrays; 13 | import java.util.List; 14 | 15 | import io.reactivex.ObservableEmitter; 16 | import io.reactivex.ObservableOnSubscribe; 17 | import io.reactivex.disposables.Disposables; 18 | import io.reactivex.functions.Action; 19 | 20 | 21 | public abstract class BaseObservableOnSubscribe implements ObservableOnSubscribe { 22 | private final Context ctx; 23 | private final Handler handler; 24 | private final List> services; 25 | 26 | @SafeVarargs 27 | protected BaseObservableOnSubscribe(ObservableContext ctx, Api... services) { 28 | this.ctx = ctx.getContext(); 29 | this.handler = ctx.getHandler(); 30 | this.services = Arrays.asList(services); 31 | } 32 | 33 | @Override 34 | public void subscribe(ObservableEmitter emitter) throws Exception { 35 | final GoogleApiClient apiClient = createApiClient(emitter); 36 | try { 37 | apiClient.connect(); 38 | } catch (Throwable ex) { 39 | if (!emitter.isDisposed()) { 40 | emitter.onError(ex); 41 | } 42 | } 43 | 44 | emitter.setDisposable(Disposables.fromAction(new Action() { 45 | @Override 46 | public void run() throws Exception { 47 | onDisposed(apiClient); 48 | apiClient.disconnect(); 49 | } 50 | })); 51 | } 52 | 53 | 54 | private GoogleApiClient createApiClient(ObservableEmitter emitter) { 55 | ApiClientConnectionCallbacks apiClientConnectionCallbacks = new ApiClientConnectionCallbacks(emitter); 56 | GoogleApiClient.Builder apiClientBuilder = new GoogleApiClient.Builder(ctx); 57 | 58 | for (Api service : services) { 59 | apiClientBuilder = apiClientBuilder.addApi(service); 60 | } 61 | 62 | apiClientBuilder = apiClientBuilder 63 | .addConnectionCallbacks(apiClientConnectionCallbacks) 64 | .addOnConnectionFailedListener(apiClientConnectionCallbacks); 65 | 66 | if (this.handler != null) { 67 | apiClientBuilder = apiClientBuilder.setHandler(handler); 68 | } 69 | 70 | GoogleApiClient apiClient = apiClientBuilder.build(); 71 | apiClientConnectionCallbacks.setClient(apiClient); 72 | return apiClient; 73 | } 74 | 75 | protected void onDisposed(GoogleApiClient locationClient) { 76 | } 77 | 78 | protected abstract void onGoogleApiClientReady(GoogleApiClient apiClient, ObservableEmitter emitter); 79 | 80 | private class ApiClientConnectionCallbacks implements 81 | GoogleApiClient.ConnectionCallbacks, 82 | GoogleApiClient.OnConnectionFailedListener { 83 | 84 | final private ObservableEmitter emitter; 85 | 86 | private GoogleApiClient apiClient; 87 | 88 | private ApiClientConnectionCallbacks(ObservableEmitter emitter) { 89 | this.emitter = emitter; 90 | } 91 | 92 | @Override 93 | public void onConnected(Bundle bundle) { 94 | try { 95 | onGoogleApiClientReady(apiClient, emitter); 96 | } catch (Throwable ex) { 97 | if (!emitter.isDisposed()) { 98 | emitter.onError(ex); 99 | } 100 | } 101 | } 102 | 103 | @Override 104 | public void onConnectionSuspended(int cause) { 105 | if (!emitter.isDisposed()) { 106 | emitter.onError(new GoogleAPIConnectionSuspendedException(cause)); 107 | } 108 | } 109 | 110 | @Override 111 | public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { 112 | if (!emitter.isDisposed()) { 113 | emitter.onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient.", 114 | connectionResult)); 115 | } 116 | } 117 | 118 | void setClient(GoogleApiClient client) { 119 | this.apiClient = client; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIClientObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables; 2 | 3 | import com.google.android.gms.common.api.Api; 4 | import com.google.android.gms.common.api.GoogleApiClient; 5 | 6 | import io.reactivex.Observable; 7 | import io.reactivex.ObservableEmitter; 8 | 9 | public class GoogleAPIClientObservableOnSubscribe extends BaseObservableOnSubscribe { 10 | 11 | @SafeVarargs 12 | public static Observable create(ObservableContext context, ObservableFactory factory, Api... apis) { 13 | return factory.createObservable(new GoogleAPIClientObservableOnSubscribe(context, apis)); 14 | } 15 | 16 | @SafeVarargs 17 | private GoogleAPIClientObservableOnSubscribe(ObservableContext ctx, Api... apis) { 18 | super(ctx, apis); 19 | } 20 | 21 | @Override 22 | protected void onGoogleApiClientReady(GoogleApiClient apiClient, ObservableEmitter emitter) { 23 | if (emitter.isDisposed()) return; 24 | emitter.onNext(apiClient); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionException.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables; 2 | 3 | import com.google.android.gms.common.ConnectionResult; 4 | 5 | public class GoogleAPIConnectionException extends RuntimeException { 6 | private final ConnectionResult connectionResult; 7 | 8 | GoogleAPIConnectionException(String detailMessage, ConnectionResult connectionResult) { 9 | super(detailMessage); 10 | this.connectionResult = connectionResult; 11 | } 12 | 13 | public ConnectionResult getConnectionResult() { 14 | return connectionResult; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/GoogleAPIConnectionSuspendedException.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables; 2 | 3 | public class GoogleAPIConnectionSuspendedException extends RuntimeException { 4 | private final int cause; 5 | 6 | GoogleAPIConnectionSuspendedException(int cause) { 7 | this.cause = cause; 8 | } 9 | 10 | public int getErrorCause() { 11 | return cause; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableContext.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | 6 | import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration; 7 | 8 | public class ObservableContext { 9 | private final Context context; 10 | private final Handler handler; 11 | private final boolean retryOnConnectionSuspended; 12 | 13 | public ObservableContext(Context context, ReactiveLocationProviderConfiguration configuration) { 14 | this.context = context; 15 | this.handler = configuration.getCustomCallbackHandler(); 16 | this.retryOnConnectionSuspended = configuration.isRetryOnConnectionSuspended(); 17 | } 18 | 19 | public Context getContext() { 20 | return context; 21 | } 22 | 23 | Handler getHandler() { 24 | return handler; 25 | } 26 | 27 | boolean isRetryOnConnectionSuspended() { 28 | return retryOnConnectionSuspended; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableEmitterWrapper.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables; 2 | 3 | import io.reactivex.ObservableEmitter; 4 | import io.reactivex.Observer; 5 | import io.reactivex.disposables.Disposable; 6 | 7 | public class ObservableEmitterWrapper implements Observer { 8 | private final ObservableEmitter emitter; 9 | 10 | public ObservableEmitterWrapper(ObservableEmitter emitter) { 11 | this.emitter = emitter; 12 | } 13 | 14 | @Override 15 | public void onSubscribe(Disposable d) { 16 | } 17 | 18 | @Override 19 | public void onNext(T t) { 20 | if (!emitter.isDisposed()){ 21 | emitter.onNext(t); 22 | } 23 | } 24 | 25 | @Override 26 | public void onError(Throwable e) { 27 | if (!emitter.isDisposed()) { 28 | emitter.onError(e); 29 | } 30 | } 31 | 32 | @Override 33 | public void onComplete() { 34 | if (!emitter.isDisposed()) { 35 | emitter.onComplete(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/ObservableFactory.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables; 2 | 3 | import io.reactivex.Observable; 4 | import io.reactivex.ObservableOnSubscribe; 5 | import io.reactivex.ObservableSource; 6 | import io.reactivex.ObservableTransformer; 7 | import io.reactivex.functions.BiPredicate; 8 | 9 | public class ObservableFactory { 10 | private final ObservableContext context; 11 | 12 | public ObservableFactory(ObservableContext context) { 13 | this.context = context; 14 | } 15 | 16 | public Observable createObservable(ObservableOnSubscribe source) { 17 | return Observable.create(source).compose(new RetryOnConnectionSuspension(context.isRetryOnConnectionSuspended())); 18 | } 19 | 20 | private static class RetryOnConnectionSuspension implements ObservableTransformer { 21 | private final boolean shouldRetry; 22 | 23 | RetryOnConnectionSuspension(boolean shouldRetry) { 24 | this.shouldRetry = shouldRetry; 25 | } 26 | 27 | @Override 28 | public ObservableSource apply(Observable upstream) { 29 | if (shouldRetry) { 30 | return upstream.retry(new IsConnectionSuspendedException()); 31 | } 32 | return upstream; 33 | } 34 | 35 | private static class IsConnectionSuspendedException implements BiPredicate { 36 | @Override 37 | public boolean test(Integer integer, Throwable throwable) throws Exception { 38 | return throwable instanceof GoogleAPIConnectionSuspendedException; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/PendingResultObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.google.android.gms.common.api.PendingResult; 6 | import com.google.android.gms.common.api.Result; 7 | import com.google.android.gms.common.api.ResultCallback; 8 | 9 | import io.reactivex.ObservableEmitter; 10 | import io.reactivex.ObservableOnSubscribe; 11 | import io.reactivex.disposables.Disposables; 12 | import io.reactivex.functions.Action; 13 | 14 | public class PendingResultObservableOnSubscribe implements ObservableOnSubscribe { 15 | private final PendingResult result; 16 | private boolean complete = false; 17 | 18 | public PendingResultObservableOnSubscribe(PendingResult result) { 19 | this.result = result; 20 | } 21 | 22 | @Override 23 | public void subscribe(final ObservableEmitter emitter) throws Exception { 24 | result.setResultCallback(new ResultCallback() { 25 | @Override 26 | public void onResult(@NonNull T t) { 27 | if (!emitter.isDisposed()) { 28 | emitter.onNext(t); 29 | emitter.onComplete(); 30 | } 31 | complete = true; 32 | } 33 | }); 34 | 35 | emitter.setDisposable(Disposables.fromAction(new Action() { 36 | @Override 37 | public void run() { 38 | if (!complete) { 39 | result.cancel(); 40 | } 41 | } 42 | })); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/StatusException.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables; 2 | 3 | import com.google.android.gms.common.api.Status; 4 | 5 | public class StatusException extends Throwable { 6 | private final Status status; 7 | 8 | public StatusException(Status status) { 9 | super(status.getStatusCode() + ": " + status.getStatusMessage()); 10 | this.status = status; 11 | } 12 | 13 | public Status getStatus() { 14 | return status; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/ActivityUpdatesObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.activity; 2 | 3 | import android.app.PendingIntent; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.IntentFilter; 8 | 9 | import com.google.android.gms.common.api.GoogleApiClient; 10 | import com.google.android.gms.location.ActivityRecognition; 11 | import com.google.android.gms.location.ActivityRecognitionResult; 12 | 13 | import io.reactivex.Observable; 14 | import io.reactivex.ObservableEmitter; 15 | import pl.charmas.android.reactivelocation2.observables.ObservableContext; 16 | import pl.charmas.android.reactivelocation2.observables.ObservableFactory; 17 | 18 | 19 | @SuppressWarnings("MissingPermission") 20 | public class ActivityUpdatesObservableOnSubscribe extends BaseActivityObservableOnSubscribe { 21 | private static final String ACTION_ACTIVITY_DETECTED = "pl.charmas.android.reactivelocation2.ACTION_ACTIVITY_UPDATE_DETECTED"; 22 | 23 | private final Context context; 24 | private final int detectionIntervalMilliseconds; 25 | private ActivityUpdatesBroadcastReceiver receiver; 26 | 27 | public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, int detectionIntervalMiliseconds) { 28 | return factory.createObservable(new ActivityUpdatesObservableOnSubscribe(ctx, detectionIntervalMiliseconds)); 29 | } 30 | 31 | private ActivityUpdatesObservableOnSubscribe(ObservableContext context, int detectionIntervalMilliseconds) { 32 | super(context); 33 | this.context = context.getContext(); 34 | this.detectionIntervalMilliseconds = detectionIntervalMilliseconds; 35 | } 36 | 37 | @Override 38 | protected void onGoogleApiClientReady(GoogleApiClient apiClient, ObservableEmitter emitter) { 39 | receiver = new ActivityUpdatesBroadcastReceiver(emitter); 40 | context.registerReceiver(receiver, new IntentFilter(ACTION_ACTIVITY_DETECTED)); 41 | PendingIntent receiverIntent = getReceiverPendingIntent(); 42 | ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(apiClient, detectionIntervalMilliseconds, receiverIntent); 43 | } 44 | 45 | private PendingIntent getReceiverPendingIntent() { 46 | return PendingIntent.getBroadcast(context, 0, new Intent(ACTION_ACTIVITY_DETECTED), PendingIntent.FLAG_UPDATE_CURRENT); 47 | } 48 | 49 | @Override 50 | protected void onDisposed(GoogleApiClient apiClient) { 51 | if (apiClient.isConnected()) { 52 | ActivityRecognition.ActivityRecognitionApi.removeActivityUpdates(apiClient, getReceiverPendingIntent()); 53 | } 54 | if (receiver != null) { 55 | context.unregisterReceiver(receiver); 56 | receiver = null; 57 | } 58 | } 59 | 60 | private static class ActivityUpdatesBroadcastReceiver extends BroadcastReceiver { 61 | private final ObservableEmitter emitter; 62 | 63 | public ActivityUpdatesBroadcastReceiver(ObservableEmitter emitter) { 64 | this.emitter = emitter; 65 | } 66 | 67 | @Override 68 | public void onReceive(Context context, Intent intent) { 69 | if (ActivityRecognitionResult.hasResult(intent)) { 70 | ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(intent); 71 | emitter.onNext(result); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/activity/BaseActivityObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.activity; 2 | 3 | import com.google.android.gms.location.ActivityRecognition; 4 | 5 | import pl.charmas.android.reactivelocation2.observables.BaseObservableOnSubscribe; 6 | import pl.charmas.android.reactivelocation2.observables.ObservableContext; 7 | 8 | abstract class BaseActivityObservableOnSubscribe extends BaseObservableOnSubscribe { 9 | BaseActivityObservableOnSubscribe(ObservableContext ctx) { 10 | super(ctx, ActivityRecognition.API); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/FallbackReverseGeocodeObservable.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.geocode; 2 | 3 | import android.location.Address; 4 | import android.text.TextUtils; 5 | 6 | import org.json.JSONArray; 7 | import org.json.JSONException; 8 | import org.json.JSONObject; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.IOException; 12 | import java.io.InputStreamReader; 13 | import java.net.HttpURLConnection; 14 | import java.net.URL; 15 | import java.util.ArrayList; 16 | import java.util.Collections; 17 | import java.util.List; 18 | import java.util.Locale; 19 | 20 | import io.reactivex.ObservableEmitter; 21 | import io.reactivex.ObservableOnSubscribe; 22 | 23 | class FallbackReverseGeocodeObservable implements ObservableOnSubscribe> { 24 | private final Locale locale; 25 | private final double latitude; 26 | private final double longitude; 27 | private final int maxResults; 28 | 29 | FallbackReverseGeocodeObservable(Locale locale, double latitude, double longitude, int maxResults) { 30 | this.locale = locale; 31 | this.latitude = latitude; 32 | this.longitude = longitude; 33 | this.maxResults = maxResults; 34 | } 35 | 36 | @Override 37 | public void subscribe(ObservableEmitter> emitter) throws Exception { 38 | try { 39 | List

addresses = alternativeReverseGeocodeQuery(); 40 | if (!emitter.isDisposed()) { 41 | emitter.onNext(addresses); 42 | emitter.onComplete(); 43 | } 44 | } catch (Exception ex) { 45 | if (!emitter.isDisposed()) { 46 | emitter.onError(ex); 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * This function fetches a list of addresses for the set latitude, longitude and maxResults properties from the 53 | * Google Geocode API (http://maps.googleapis.com/maps/api/geocode). 54 | * 55 | * @return List of addresses 56 | * @throws IOException In case of network problems 57 | * @throws JSONException In case of problems while parsing the json response from google geocode API servers 58 | */ 59 | private List
alternativeReverseGeocodeQuery() throws IOException, JSONException { 60 | URL url = new URL(String.format(Locale.ENGLISH, 61 | "http://maps.googleapis.com/maps/api/geocode/json?" 62 | + "latlng=%1$f,%2$f&sensor=true&language=%3$s", 63 | latitude, longitude, locale.getLanguage() 64 | )); 65 | HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); 66 | StringBuilder stringBuilder = new StringBuilder(); 67 | List
outResult = new ArrayList<>(); 68 | 69 | try { 70 | BufferedReader reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8")); 71 | String line; 72 | while ((line = reader.readLine()) != null) { 73 | stringBuilder.append(line); 74 | } 75 | 76 | // Root json response object 77 | JSONObject jsonRootObject = new JSONObject(stringBuilder.toString()); 78 | 79 | // No results status 80 | if ("ZERO_RESULTS".equalsIgnoreCase(jsonRootObject.getString("status"))) { 81 | return Collections.emptyList(); 82 | } 83 | 84 | // Other non-OK responses status 85 | if (!"OK".equalsIgnoreCase(jsonRootObject.getString("status"))) { 86 | throw new RuntimeException("Wrong API response"); 87 | } 88 | 89 | // Process results 90 | JSONArray results = jsonRootObject.getJSONArray("results"); 91 | for (int i = 0; i < results.length() && i < maxResults; i++) { 92 | Address address = new Address(Locale.getDefault()); 93 | String addressLineString = ""; 94 | JSONObject sourceResult = results.getJSONObject(i); 95 | JSONArray addressComponents = sourceResult.getJSONArray("address_components"); 96 | 97 | // Assemble address by various components 98 | for (int ac = 0; ac < addressComponents.length(); ac++) { 99 | String longNameVal = addressComponents.getJSONObject(ac).getString("long_name"); 100 | String shortNameVal = addressComponents.getJSONObject(ac).getString("short_name"); 101 | JSONArray acTypes = addressComponents.getJSONObject(ac).getJSONArray("types"); 102 | String acType = acTypes.getString(0); 103 | 104 | if (!TextUtils.isEmpty(longNameVal)) { 105 | if (acType.equalsIgnoreCase("street_number")) { 106 | if (TextUtils.isEmpty(addressLineString)) { 107 | addressLineString = longNameVal; 108 | } else { 109 | addressLineString += " " + longNameVal; 110 | } 111 | } else if (acType.equalsIgnoreCase("route")) { 112 | if (TextUtils.isEmpty(addressLineString)) { 113 | addressLineString = longNameVal; 114 | } else { 115 | addressLineString = longNameVal + " " + addressLineString; 116 | } 117 | } else if (acType.equalsIgnoreCase("sublocality")) { 118 | address.setSubLocality(longNameVal); 119 | } else if (acType.equalsIgnoreCase("locality")) { 120 | address.setLocality(longNameVal); 121 | } else if (acType.equalsIgnoreCase("administrative_area_level_2")) { 122 | address.setSubAdminArea(longNameVal); 123 | } else if (acType.equalsIgnoreCase("administrative_area_level_1")) { 124 | address.setAdminArea(longNameVal); 125 | } else if (acType.equalsIgnoreCase("country")) { 126 | address.setCountryName(longNameVal); 127 | address.setCountryCode(shortNameVal); 128 | } else if (acType.equalsIgnoreCase("postal_code")) { 129 | address.setPostalCode(longNameVal); 130 | } 131 | } 132 | } 133 | 134 | // Try to get the already formatted address 135 | String formattedAddress = sourceResult.getString("formatted_address"); 136 | if (!TextUtils.isEmpty(formattedAddress)) { 137 | String[] formattedAddressLines = formattedAddress.split(","); 138 | 139 | for (int ia = 0; ia < formattedAddressLines.length; ia++) { 140 | address.setAddressLine(ia, formattedAddressLines[ia].trim()); 141 | } 142 | } else if (!TextUtils.isEmpty(addressLineString)) { 143 | // If that fails use our manually assembled formatted address 144 | address.setAddressLine(0, addressLineString); 145 | } 146 | 147 | // Finally add address to resulting set 148 | outResult.add(address); 149 | } 150 | 151 | } finally { 152 | urlConnection.disconnect(); 153 | } 154 | 155 | return Collections.unmodifiableList(outResult); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/GeocodeObservable.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.geocode; 2 | 3 | import android.content.Context; 4 | import android.location.Address; 5 | import android.location.Geocoder; 6 | import android.support.annotation.NonNull; 7 | 8 | import com.google.android.gms.maps.model.LatLngBounds; 9 | 10 | import java.io.IOException; 11 | import java.util.List; 12 | import java.util.Locale; 13 | 14 | import io.reactivex.Observable; 15 | import io.reactivex.ObservableEmitter; 16 | import io.reactivex.ObservableOnSubscribe; 17 | import pl.charmas.android.reactivelocation2.observables.ObservableFactory; 18 | 19 | public class GeocodeObservable implements ObservableOnSubscribe> { 20 | private final Context ctx; 21 | private final String locationName; 22 | private final int maxResults; 23 | private final LatLngBounds bounds; 24 | private final Locale locale; 25 | 26 | public static Observable> createObservable(Context ctx, ObservableFactory factory, String locationName, int maxResults, LatLngBounds bounds, Locale locale) { 27 | return factory.createObservable(new GeocodeObservable(ctx, locationName, maxResults, bounds, locale)); 28 | } 29 | 30 | private GeocodeObservable(Context ctx, String locationName, int maxResults, LatLngBounds bounds, Locale locale) { 31 | this.ctx = ctx; 32 | this.locationName = locationName; 33 | this.maxResults = maxResults; 34 | this.bounds = bounds; 35 | this.locale = locale; 36 | } 37 | 38 | @Override 39 | public void subscribe(ObservableEmitter> emitter) throws Exception { 40 | Geocoder geocoder = createGeocoder(); 41 | try { 42 | List
result = getAddresses(geocoder); 43 | if (!emitter.isDisposed()) { 44 | emitter.onNext(result); 45 | emitter.onComplete(); 46 | } 47 | } catch (IOException e) { 48 | if (!emitter.isDisposed()) { 49 | emitter.onError(e); 50 | } 51 | } 52 | } 53 | 54 | private List
getAddresses(Geocoder geocoder) throws IOException { 55 | List
result; 56 | if (bounds != null) { 57 | result = geocoder.getFromLocationName(locationName, maxResults, bounds.southwest.latitude, bounds.southwest.longitude, bounds.northeast.latitude, bounds.northeast.longitude); 58 | } else { 59 | result = geocoder.getFromLocationName(locationName, maxResults); 60 | } 61 | return result; 62 | } 63 | 64 | @NonNull 65 | private Geocoder createGeocoder() { 66 | if (locale != null) return new Geocoder(ctx, locale); 67 | return new Geocoder(ctx); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geocode/ReverseGeocodeObservable.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.geocode; 2 | 3 | import android.content.Context; 4 | import android.location.Address; 5 | import android.location.Geocoder; 6 | 7 | import java.io.IOException; 8 | import java.util.List; 9 | import java.util.Locale; 10 | 11 | import io.reactivex.Observable; 12 | import io.reactivex.ObservableEmitter; 13 | import io.reactivex.ObservableOnSubscribe; 14 | import io.reactivex.schedulers.Schedulers; 15 | import pl.charmas.android.reactivelocation2.observables.ObservableEmitterWrapper; 16 | import pl.charmas.android.reactivelocation2.observables.ObservableFactory; 17 | 18 | 19 | public class ReverseGeocodeObservable implements ObservableOnSubscribe> { 20 | private final Context ctx; 21 | private final Locale locale; 22 | private final double latitude; 23 | private final double longitude; 24 | private final int maxResults; 25 | 26 | public static Observable> createObservable(Context ctx, ObservableFactory factory, Locale locale, double latitude, double longitude, int maxResults) { 27 | return factory.createObservable(new ReverseGeocodeObservable(ctx, locale, latitude, longitude, maxResults)); 28 | } 29 | 30 | private ReverseGeocodeObservable(Context ctx, Locale locale, double latitude, double longitude, int maxResults) { 31 | this.ctx = ctx; 32 | this.latitude = latitude; 33 | this.longitude = longitude; 34 | this.maxResults = maxResults; 35 | this.locale = locale; 36 | } 37 | 38 | @Override 39 | public void subscribe(ObservableEmitter> emitter) throws Exception { 40 | Geocoder geocoder = new Geocoder(ctx, locale); 41 | try { 42 | List
addresses = geocoder.getFromLocation(latitude, longitude, maxResults); 43 | if (!emitter.isDisposed()) { 44 | emitter.onNext(addresses); 45 | emitter.onComplete(); 46 | } 47 | } catch (IOException e) { 48 | // If it's a service not available error try a different approach using google web api 49 | if (!emitter.isDisposed()) { 50 | Observable 51 | .create(new FallbackReverseGeocodeObservable(locale, latitude, longitude, maxResults)) 52 | .subscribeOn(Schedulers.io()) 53 | .subscribe(new ObservableEmitterWrapper<>(emitter)); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/AddGeofenceObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.geofence; 2 | 3 | import android.app.PendingIntent; 4 | import android.support.annotation.NonNull; 5 | 6 | import com.google.android.gms.common.api.GoogleApiClient; 7 | import com.google.android.gms.common.api.ResultCallback; 8 | import com.google.android.gms.common.api.Status; 9 | import com.google.android.gms.location.GeofencingRequest; 10 | import com.google.android.gms.location.LocationServices; 11 | 12 | import io.reactivex.Observable; 13 | import io.reactivex.ObservableEmitter; 14 | import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; 15 | import pl.charmas.android.reactivelocation2.observables.ObservableContext; 16 | import pl.charmas.android.reactivelocation2.observables.ObservableFactory; 17 | import pl.charmas.android.reactivelocation2.observables.StatusException; 18 | 19 | 20 | @SuppressWarnings("MissingPermission") 21 | public class AddGeofenceObservableOnSubscribe extends BaseLocationObservableOnSubscribe { 22 | private final GeofencingRequest request; 23 | private final PendingIntent geofenceTransitionPendingIntent; 24 | 25 | public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, GeofencingRequest request, PendingIntent geofenceTransitionPendingIntent) { 26 | return factory.createObservable(new AddGeofenceObservableOnSubscribe(ctx, request, geofenceTransitionPendingIntent)); 27 | } 28 | 29 | private AddGeofenceObservableOnSubscribe(ObservableContext ctx, GeofencingRequest request, PendingIntent geofenceTransitionPendingIntent) { 30 | super(ctx); 31 | this.request = request; 32 | this.geofenceTransitionPendingIntent = geofenceTransitionPendingIntent; 33 | } 34 | 35 | @Override 36 | protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { 37 | LocationServices.GeofencingApi.addGeofences(apiClient, request, geofenceTransitionPendingIntent) 38 | .setResultCallback(new ResultCallback() { 39 | @Override 40 | public void onResult(@NonNull Status status) { 41 | if (emitter.isDisposed()) return; 42 | if (status.isSuccess()) { 43 | emitter.onNext(status); 44 | emitter.onComplete(); 45 | 46 | } else { 47 | emitter.onError(new StatusException(status)); 48 | } 49 | } 50 | }); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceByPendingIntentObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.geofence; 2 | 3 | import android.app.PendingIntent; 4 | import android.support.annotation.NonNull; 5 | 6 | import com.google.android.gms.common.api.GoogleApiClient; 7 | import com.google.android.gms.common.api.ResultCallback; 8 | import com.google.android.gms.common.api.Status; 9 | import com.google.android.gms.location.LocationServices; 10 | 11 | import io.reactivex.ObservableEmitter; 12 | import pl.charmas.android.reactivelocation2.observables.ObservableContext; 13 | import pl.charmas.android.reactivelocation2.observables.StatusException; 14 | 15 | 16 | class RemoveGeofenceByPendingIntentObservableOnSubscribe extends RemoveGeofenceObservableOnSubscribe { 17 | private final PendingIntent pendingIntent; 18 | 19 | RemoveGeofenceByPendingIntentObservableOnSubscribe(ObservableContext ctx, PendingIntent pendingIntent) { 20 | super(ctx); 21 | this.pendingIntent = pendingIntent; 22 | } 23 | 24 | @Override 25 | protected void removeGeofences(GoogleApiClient locationClient, final ObservableEmitter emitter) { 26 | LocationServices.GeofencingApi.removeGeofences(locationClient, pendingIntent) 27 | .setResultCallback(new ResultCallback() { 28 | @Override 29 | public void onResult(@NonNull Status status) { 30 | if (emitter.isDisposed()) return; 31 | if (status.isSuccess()) { 32 | emitter.onNext(status); 33 | emitter.onComplete(); 34 | } else { 35 | emitter.onError(new StatusException(status)); 36 | } 37 | } 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.geofence; 2 | 3 | import android.app.PendingIntent; 4 | 5 | import com.google.android.gms.common.api.GoogleApiClient; 6 | import com.google.android.gms.common.api.Status; 7 | 8 | import java.util.List; 9 | 10 | import io.reactivex.Observable; 11 | import io.reactivex.ObservableEmitter; 12 | import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; 13 | import pl.charmas.android.reactivelocation2.observables.ObservableContext; 14 | import pl.charmas.android.reactivelocation2.observables.ObservableFactory; 15 | 16 | 17 | public abstract class RemoveGeofenceObservableOnSubscribe extends BaseLocationObservableOnSubscribe { 18 | 19 | public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, PendingIntent pendingIntent) { 20 | return factory.createObservable(new RemoveGeofenceByPendingIntentObservableOnSubscribe(ctx, pendingIntent)); 21 | } 22 | 23 | public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, List requestIds) { 24 | return factory.createObservable(new RemoveGeofenceRequestIdsObservableOnSubscribe(ctx, requestIds)); 25 | } 26 | 27 | RemoveGeofenceObservableOnSubscribe(ObservableContext ctx) { 28 | super(ctx); 29 | } 30 | 31 | @Override 32 | protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { 33 | removeGeofences(apiClient, emitter); 34 | } 35 | 36 | protected abstract void removeGeofences(GoogleApiClient locationClient, ObservableEmitter emitter); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/geofence/RemoveGeofenceRequestIdsObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.geofence; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import com.google.android.gms.common.api.GoogleApiClient; 6 | import com.google.android.gms.common.api.ResultCallback; 7 | import com.google.android.gms.common.api.Status; 8 | import com.google.android.gms.location.LocationServices; 9 | 10 | import java.util.List; 11 | 12 | import io.reactivex.ObservableEmitter; 13 | import pl.charmas.android.reactivelocation2.observables.ObservableContext; 14 | import pl.charmas.android.reactivelocation2.observables.StatusException; 15 | 16 | 17 | class RemoveGeofenceRequestIdsObservableOnSubscribe extends RemoveGeofenceObservableOnSubscribe { 18 | private final List geofenceRequestIds; 19 | 20 | RemoveGeofenceRequestIdsObservableOnSubscribe(ObservableContext ctx, List geofenceRequestIds) { 21 | super(ctx); 22 | this.geofenceRequestIds = geofenceRequestIds; 23 | } 24 | 25 | @Override 26 | protected void removeGeofences(GoogleApiClient locationClient, final ObservableEmitter emitter) { 27 | LocationServices.GeofencingApi.removeGeofences(locationClient, geofenceRequestIds) 28 | .setResultCallback(new ResultCallback() { 29 | @Override 30 | public void onResult(@NonNull Status status) { 31 | if (emitter.isDisposed()) return; 32 | if (status.isSuccess()) { 33 | emitter.onNext(status); 34 | emitter.onComplete(); 35 | } else { 36 | emitter.onError(new StatusException(status)); 37 | } 38 | } 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/AddLocationIntentUpdatesObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.location; 2 | 3 | import android.app.PendingIntent; 4 | import android.support.annotation.NonNull; 5 | 6 | import com.google.android.gms.common.api.GoogleApiClient; 7 | import com.google.android.gms.common.api.ResultCallback; 8 | import com.google.android.gms.common.api.Status; 9 | import com.google.android.gms.location.LocationRequest; 10 | import com.google.android.gms.location.LocationServices; 11 | 12 | import io.reactivex.Observable; 13 | import io.reactivex.ObservableEmitter; 14 | import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; 15 | import pl.charmas.android.reactivelocation2.observables.ObservableContext; 16 | import pl.charmas.android.reactivelocation2.observables.ObservableFactory; 17 | import pl.charmas.android.reactivelocation2.observables.StatusException; 18 | 19 | 20 | @SuppressWarnings("MissingPermission") 21 | public class AddLocationIntentUpdatesObservableOnSubscribe extends BaseLocationObservableOnSubscribe { 22 | private final LocationRequest locationRequest; 23 | private final PendingIntent intent; 24 | 25 | public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, LocationRequest locationRequest, PendingIntent intent) { 26 | return factory.createObservable(new AddLocationIntentUpdatesObservableOnSubscribe(ctx, locationRequest, intent)); 27 | } 28 | 29 | private AddLocationIntentUpdatesObservableOnSubscribe(ObservableContext ctx, LocationRequest locationRequest, PendingIntent intent) { 30 | super(ctx); 31 | this.locationRequest = locationRequest; 32 | this.intent = intent; 33 | } 34 | 35 | @Override 36 | protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { 37 | LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, locationRequest, intent) 38 | .setResultCallback(new ResultCallback() { 39 | @Override 40 | public void onResult(@NonNull Status status) { 41 | if (emitter.isDisposed()) return; 42 | if (!status.isSuccess()) { 43 | emitter.onError(new StatusException(status)); 44 | } else { 45 | emitter.onNext(status); 46 | emitter.onComplete(); 47 | } 48 | } 49 | }); 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LastKnownLocationObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.location; 2 | 3 | import android.location.Location; 4 | 5 | import com.google.android.gms.common.api.GoogleApiClient; 6 | import com.google.android.gms.location.LocationServices; 7 | 8 | import io.reactivex.Observable; 9 | import io.reactivex.ObservableEmitter; 10 | import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; 11 | import pl.charmas.android.reactivelocation2.observables.ObservableContext; 12 | import pl.charmas.android.reactivelocation2.observables.ObservableFactory; 13 | 14 | @SuppressWarnings("MissingPermission") 15 | public class LastKnownLocationObservableOnSubscribe extends BaseLocationObservableOnSubscribe { 16 | public static Observable createObservable(ObservableContext ctx, ObservableFactory factory) { 17 | return factory.createObservable(new LastKnownLocationObservableOnSubscribe(ctx)); 18 | } 19 | 20 | private LastKnownLocationObservableOnSubscribe(ObservableContext ctx) { 21 | super(ctx); 22 | } 23 | 24 | @Override 25 | protected void onGoogleApiClientReady(GoogleApiClient apiClient, ObservableEmitter emitter) { 26 | Location location = LocationServices.FusedLocationApi.getLastLocation(apiClient); 27 | if (emitter.isDisposed()) return; 28 | if (location != null) { 29 | emitter.onNext(location); 30 | } 31 | emitter.onComplete(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/LocationUpdatesObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.location; 2 | 3 | import android.location.Location; 4 | 5 | import com.google.android.gms.common.api.GoogleApiClient; 6 | import com.google.android.gms.location.LocationListener; 7 | import com.google.android.gms.location.LocationRequest; 8 | import com.google.android.gms.location.LocationServices; 9 | 10 | import java.lang.ref.WeakReference; 11 | 12 | import io.reactivex.Observable; 13 | import io.reactivex.ObservableEmitter; 14 | import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; 15 | import pl.charmas.android.reactivelocation2.observables.ObservableContext; 16 | import pl.charmas.android.reactivelocation2.observables.ObservableFactory; 17 | 18 | 19 | @SuppressWarnings("MissingPermission") 20 | public class LocationUpdatesObservableOnSubscribe extends BaseLocationObservableOnSubscribe { 21 | public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, LocationRequest locationRequest) { 22 | Observable observable = factory.createObservable(new LocationUpdatesObservableOnSubscribe(ctx, locationRequest)); 23 | int requestedNumberOfUpdates = locationRequest.getNumUpdates(); 24 | if (requestedNumberOfUpdates > 0 && requestedNumberOfUpdates < Integer.MAX_VALUE) { 25 | observable = observable.take(requestedNumberOfUpdates); 26 | } 27 | return observable; 28 | } 29 | 30 | private final LocationRequest locationRequest; 31 | private LocationListener listener; 32 | 33 | private LocationUpdatesObservableOnSubscribe(ObservableContext ctx, LocationRequest locationRequest) { 34 | super(ctx); 35 | this.locationRequest = locationRequest; 36 | } 37 | 38 | @Override 39 | protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { 40 | listener = new LocationUpdatesLocationListener(emitter); 41 | LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, locationRequest, listener); 42 | } 43 | 44 | @Override 45 | protected void onDisposed(GoogleApiClient locationClient) { 46 | if (locationClient.isConnected()) { 47 | LocationServices.FusedLocationApi.removeLocationUpdates(locationClient, listener); 48 | } 49 | } 50 | 51 | private static class LocationUpdatesLocationListener implements LocationListener { 52 | private final WeakReference> weakRef; 53 | 54 | LocationUpdatesLocationListener(ObservableEmitter emitter) { 55 | this.weakRef = new WeakReference>(emitter); 56 | } 57 | 58 | @Override 59 | public void onLocationChanged(Location location) { 60 | final ObservableEmitter observer = weakRef.get(); 61 | if (observer != null && !observer.isDisposed()) { 62 | observer.onNext(location); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/MockLocationObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.location; 2 | 3 | import android.location.Location; 4 | import android.support.annotation.NonNull; 5 | 6 | import com.google.android.gms.common.api.GoogleApiClient; 7 | import com.google.android.gms.common.api.ResultCallback; 8 | import com.google.android.gms.common.api.Status; 9 | import com.google.android.gms.location.LocationServices; 10 | 11 | import io.reactivex.Observable; 12 | import io.reactivex.ObservableEmitter; 13 | import io.reactivex.disposables.Disposable; 14 | import io.reactivex.functions.Action; 15 | import io.reactivex.functions.Consumer; 16 | import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; 17 | import pl.charmas.android.reactivelocation2.observables.ObservableContext; 18 | import pl.charmas.android.reactivelocation2.observables.ObservableFactory; 19 | import pl.charmas.android.reactivelocation2.observables.StatusException; 20 | 21 | @SuppressWarnings("MissingPermission") 22 | public class MockLocationObservableOnSubscribe extends BaseLocationObservableOnSubscribe { 23 | private final Observable locationObservable; 24 | private Disposable mockLocationSubscription; 25 | 26 | public static Observable createObservable(ObservableContext context, ObservableFactory factory, Observable locationObservable) { 27 | return factory.createObservable(new MockLocationObservableOnSubscribe(context, locationObservable)); 28 | } 29 | 30 | private MockLocationObservableOnSubscribe(ObservableContext ctx, Observable locationObservable) { 31 | super(ctx); 32 | this.locationObservable = locationObservable; 33 | } 34 | 35 | @Override 36 | protected void onGoogleApiClientReady(final GoogleApiClient apiClient, final ObservableEmitter emitter) { 37 | // this throws SecurityException if permissions are bad or mock locations are not enabled, 38 | // which is passed to observer's onError by BaseObservable 39 | LocationServices.FusedLocationApi.setMockMode(apiClient, true) 40 | .setResultCallback(new ResultCallback() { 41 | @Override 42 | public void onResult(@NonNull Status status) { 43 | if (status.isSuccess()) { 44 | startLocationMocking(apiClient, emitter); 45 | } else { 46 | emitter.onError(new StatusException(status)); 47 | } 48 | } 49 | }); 50 | } 51 | 52 | private void startLocationMocking(final GoogleApiClient apiClient, final ObservableEmitter emitter) { 53 | mockLocationSubscription = locationObservable 54 | .subscribe(new Consumer() { 55 | @Override 56 | public void accept(Location location) throws Exception { 57 | LocationServices.FusedLocationApi.setMockLocation(apiClient, location) 58 | .setResultCallback(new ResultCallback() { 59 | @Override 60 | public void onResult(@NonNull Status status) { 61 | if (status.isSuccess()) { 62 | emitter.onNext(status); 63 | } else { 64 | emitter.onError(new StatusException(status)); 65 | } 66 | } 67 | }); 68 | } 69 | }, 70 | 71 | new Consumer() { 72 | @Override 73 | public void accept(Throwable throwable) throws Exception { 74 | emitter.onError(throwable); 75 | } 76 | 77 | }, new Action() { 78 | @Override 79 | public void run() throws Exception { 80 | emitter.onComplete(); 81 | } 82 | }); 83 | 84 | } 85 | 86 | @Override 87 | protected void onDisposed(GoogleApiClient locationClient) { 88 | if (locationClient.isConnected()) { 89 | try { 90 | LocationServices.FusedLocationApi.setMockMode(locationClient, false); 91 | } catch (SecurityException e) { 92 | // if this happens then we couldn't have switched mock mode on in the first place, 93 | // and the observer's onError will already have been called 94 | } 95 | } 96 | if (mockLocationSubscription != null && !mockLocationSubscription.isDisposed()) { 97 | mockLocationSubscription.dispose(); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /android-reactive-location/src/main/java/pl/charmas/android/reactivelocation2/observables/location/RemoveLocationIntentUpdatesObservableOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.observables.location; 2 | 3 | import android.app.PendingIntent; 4 | import android.support.annotation.NonNull; 5 | 6 | import com.google.android.gms.common.api.GoogleApiClient; 7 | import com.google.android.gms.common.api.ResultCallback; 8 | import com.google.android.gms.common.api.Status; 9 | import com.google.android.gms.location.LocationServices; 10 | 11 | import io.reactivex.Observable; 12 | import io.reactivex.ObservableEmitter; 13 | import pl.charmas.android.reactivelocation2.observables.BaseLocationObservableOnSubscribe; 14 | import pl.charmas.android.reactivelocation2.observables.ObservableContext; 15 | import pl.charmas.android.reactivelocation2.observables.ObservableFactory; 16 | import pl.charmas.android.reactivelocation2.observables.StatusException; 17 | 18 | 19 | public class RemoveLocationIntentUpdatesObservableOnSubscribe extends BaseLocationObservableOnSubscribe { 20 | private final PendingIntent intent; 21 | 22 | public static Observable createObservable(ObservableContext ctx, ObservableFactory factory, PendingIntent intent) { 23 | return factory.createObservable(new RemoveLocationIntentUpdatesObservableOnSubscribe(ctx, intent)); 24 | } 25 | 26 | private RemoveLocationIntentUpdatesObservableOnSubscribe(ObservableContext ctx, PendingIntent intent) { 27 | super(ctx); 28 | this.intent = intent; 29 | } 30 | 31 | @Override 32 | protected void onGoogleApiClientReady(GoogleApiClient apiClient, final ObservableEmitter emitter) { 33 | LocationServices.FusedLocationApi.removeLocationUpdates(apiClient, intent) 34 | .setResultCallback(new ResultCallback() { 35 | @Override 36 | public void onResult(@NonNull Status status) { 37 | if (emitter.isDisposed()) return; 38 | if (status.isSuccess()) { 39 | emitter.onNext(status); 40 | emitter.onComplete(); 41 | } else { 42 | emitter.onError(new StatusException(status)); 43 | } 44 | } 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:2.3.3' 10 | } 11 | } 12 | 13 | // To avoid manually setting the same values in all Android modules, set the value on the root 14 | // project and then reference this from the modules 15 | ext { 16 | compileSdkVersion = 25 17 | targetSdkVersion = 25 18 | buildToolsVersion = "25.0.3" 19 | } 20 | 21 | def isReleaseBuild() { 22 | return version.contains("SNAPSHOT") == false 23 | } 24 | 25 | allprojects { 26 | version = VERSION_NAME 27 | group = GROUP 28 | 29 | repositories { 30 | mavenCentral() 31 | jcenter() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=2.1 2 | VERSION_CODE=102 3 | GROUP=pl.charmas.android 4 | 5 | 6 | POM_DESCRIPTION=Small library that wraps Google Play Service API in brilliant RxJava Observables reducing boilerplate to minimum. 7 | POM_URL=https://github.com/mcharmas/Android-ReactiveLocation 8 | POM_SCM_URL=https://github.com/mcharmas/Android-ReactiveLocation 9 | POM_SCM_CONNECTION=scm:git@github.com:mcharmas/Android-ReactiveLocation.git 10 | POM_SCM_DEV_CONNECTION=scm:git@github.com:mcharmas/Android-ReactiveLocation.git 11 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 12 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 13 | POM_LICENCE_DIST=repo 14 | POM_DEVELOPER_ID=mcharmas 15 | POM_DEVELOPER_NAME=Michal Charmas 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcharmas/Android-ReactiveLocation/8dce277e49540767965b70502567af93593de994/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Sep 29 10:11:52 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /maven_push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Chris Banes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | def isReleaseBuild() { 21 | return VERSION_NAME.contains("SNAPSHOT") == false 22 | } 23 | 24 | def getReleaseRepositoryUrl() { 25 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 26 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 27 | } 28 | 29 | def getSnapshotRepositoryUrl() { 30 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 31 | : "https://oss.sonatype.org/content/repositories/snapshots/" 32 | } 33 | 34 | def getRepositoryUsername() { 35 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" 36 | } 37 | 38 | def getRepositoryPassword() { 39 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" 40 | } 41 | 42 | afterEvaluate { project -> 43 | uploadArchives { 44 | repositories { 45 | mavenDeployer { 46 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 47 | 48 | pom.groupId = GROUP 49 | pom.artifactId = POM_ARTIFACT_ID 50 | pom.version = VERSION_NAME 51 | 52 | repository(url: getReleaseRepositoryUrl()) { 53 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 54 | } 55 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 57 | } 58 | 59 | pom.project { 60 | name POM_NAME 61 | packaging POM_PACKAGING 62 | description POM_DESCRIPTION 63 | url POM_URL 64 | 65 | scm { 66 | url POM_SCM_URL 67 | connection POM_SCM_CONNECTION 68 | developerConnection POM_SCM_DEV_CONNECTION 69 | } 70 | 71 | licenses { 72 | license { 73 | name POM_LICENCE_NAME 74 | url POM_LICENCE_URL 75 | distribution POM_LICENCE_DIST 76 | } 77 | } 78 | 79 | developers { 80 | developer { 81 | id POM_DEVELOPER_ID 82 | name POM_DEVELOPER_NAME 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | signing { 91 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 92 | sign configurations.archives 93 | } 94 | 95 | task androidJavadocs(type: Javadoc) { 96 | source = android.sourceSets.main.java.srcDirs 97 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 98 | failOnError = false 99 | } 100 | 101 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 102 | classifier = 'javadoc' 103 | from androidJavadocs.destinationDir 104 | } 105 | 106 | task androidSourcesJar(type: Jar) { 107 | classifier = 'sources' 108 | from android.sourceSets.main.java.sourceFiles 109 | } 110 | 111 | artifacts { 112 | archives androidSourcesJar 113 | archives androidJavadocsJar 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | def getGooglePlayServicesApiKey() { 4 | if (REACTIVE_LOCATION_GMS_API_KEY != null && !REACTIVE_LOCATION_GMS_API_KEY.isEmpty()) { 5 | return REACTIVE_LOCATION_GMS_API_KEY; 6 | } 7 | return "" 8 | } 9 | 10 | android { 11 | compileSdkVersion rootProject.ext.compileSdkVersion 12 | buildToolsVersion rootProject.ext.buildToolsVersion 13 | 14 | defaultConfig { 15 | minSdkVersion 14 16 | targetSdkVersion rootProject.ext.targetSdkVersion 17 | versionName project.VERSION_NAME 18 | versionCode Integer.parseInt(project.VERSION_CODE) 19 | resValue "string", "API_KEY", getGooglePlayServicesApiKey() 20 | } 21 | 22 | compileOptions { 23 | sourceCompatibility JavaVersion.VERSION_1_7 24 | targetCompatibility JavaVersion.VERSION_1_7 25 | } 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 30 | } 31 | } 32 | lintOptions { 33 | abortOnError false 34 | } 35 | } 36 | 37 | dependencies { 38 | compile 'com.android.support:appcompat-v7:25.3.1' 39 | compile 'io.reactivex.rxjava2:rxandroid:2.0.1' 40 | compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.1@aar' 41 | compile project(':android-reactive-location') 42 | } 43 | -------------------------------------------------------------------------------- /sample/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/orbit/utils/android-studio/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 10 | 11 | 16 | 17 | 20 | 21 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample; 2 | 3 | import android.Manifest; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.widget.Toast; 6 | 7 | import com.tbruyelle.rxpermissions2.RxPermissions; 8 | 9 | import io.reactivex.functions.Consumer; 10 | 11 | public abstract class BaseActivity extends AppCompatActivity { 12 | 13 | @Override 14 | protected void onStart() { 15 | super.onStart(); 16 | new RxPermissions(this) 17 | .request(Manifest.permission.ACCESS_FINE_LOCATION) 18 | .subscribe(new Consumer() { 19 | @Override 20 | public void accept(Boolean granted) throws Exception { 21 | if (granted) { 22 | onLocationPermissionGranted(); 23 | } else { 24 | Toast.makeText(BaseActivity.this, "Sorry, no demo without permission...", Toast.LENGTH_SHORT).show(); 25 | } 26 | } 27 | }); 28 | } 29 | 30 | protected abstract void onLocationPermissionGranted(); 31 | } 32 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceActivity.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample; 2 | 3 | import android.app.PendingIntent; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.widget.EditText; 9 | import android.widget.TextView; 10 | import android.widget.Toast; 11 | 12 | import com.google.android.gms.common.api.Status; 13 | import com.google.android.gms.location.Geofence; 14 | import com.google.android.gms.location.GeofencingRequest; 15 | 16 | import io.reactivex.Observable; 17 | import io.reactivex.disposables.Disposable; 18 | import io.reactivex.functions.Consumer; 19 | import io.reactivex.functions.Function; 20 | import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; 21 | import pl.charmas.android.reactivelocation2.sample.utils.DisplayTextOnViewAction; 22 | import pl.charmas.android.reactivelocation2.sample.utils.LocationToStringFunc; 23 | 24 | import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; 25 | 26 | public class GeofenceActivity extends BaseActivity { 27 | private static final String TAG = "GeofenceActivity"; 28 | 29 | private ReactiveLocationProvider reactiveLocationProvider; 30 | private EditText latitudeInput; 31 | private EditText longitudeInput; 32 | private EditText radiusInput; 33 | private TextView lastKnownLocationView; 34 | private Disposable lastKnownLocationDisposable; 35 | 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | reactiveLocationProvider = new ReactiveLocationProvider(this); 40 | setContentView(R.layout.activity_geofence); 41 | initViews(); 42 | } 43 | 44 | private void initViews() { 45 | lastKnownLocationView = (TextView) findViewById(R.id.last_known_location_view); 46 | latitudeInput = (EditText) findViewById(R.id.latitude_input); 47 | longitudeInput = (EditText) findViewById(R.id.longitude_input); 48 | radiusInput = (EditText) findViewById(R.id.radius_input); 49 | findViewById(R.id.add_button).setOnClickListener(new View.OnClickListener() { 50 | @Override 51 | public void onClick(View v) { 52 | addGeofence(); 53 | } 54 | }); 55 | findViewById(R.id.clear_button).setOnClickListener(new View.OnClickListener() { 56 | @Override 57 | public void onClick(View v) { 58 | clearGeofence(); 59 | } 60 | }); 61 | } 62 | 63 | @Override 64 | protected void onLocationPermissionGranted() { 65 | lastKnownLocationDisposable = reactiveLocationProvider 66 | .getLastKnownLocation() 67 | .map(new LocationToStringFunc()) 68 | .subscribe(new DisplayTextOnViewAction(lastKnownLocationView)); 69 | } 70 | 71 | @Override 72 | protected void onStop() { 73 | super.onStop(); 74 | dispose(lastKnownLocationDisposable); 75 | } 76 | 77 | private void clearGeofence() { 78 | reactiveLocationProvider 79 | .removeGeofences(createNotificationBroadcastPendingIntent()) 80 | .subscribe(new Consumer() { 81 | @Override 82 | public void accept(Status status) throws Exception { 83 | toast("Geofences removed"); 84 | } 85 | }, new Consumer() { 86 | @Override 87 | public void accept(Throwable throwable) throws Exception { 88 | toast("Error removing geofences"); 89 | Log.d(TAG, "Error removing geofences", throwable); 90 | } 91 | }); 92 | } 93 | 94 | private void toast(String text) { 95 | Toast.makeText(GeofenceActivity.this, text, Toast.LENGTH_SHORT).show(); 96 | } 97 | 98 | private PendingIntent createNotificationBroadcastPendingIntent() { 99 | return PendingIntent.getBroadcast(this, 0, new Intent(this, GeofenceBroadcastReceiver.class), PendingIntent.FLAG_UPDATE_CURRENT); 100 | } 101 | 102 | private void addGeofence() { 103 | final GeofencingRequest geofencingRequest = createGeofencingRequest(); 104 | if (geofencingRequest == null) return; 105 | 106 | final PendingIntent pendingIntent = createNotificationBroadcastPendingIntent(); 107 | 108 | reactiveLocationProvider 109 | .removeGeofences(pendingIntent) 110 | .flatMap(new Function>() { 111 | @Override 112 | public Observable apply(Status status) throws Exception { 113 | return reactiveLocationProvider.addGeofences(pendingIntent, geofencingRequest); 114 | } 115 | 116 | }) 117 | .subscribe(new Consumer() { 118 | @Override 119 | public void accept(Status addGeofenceResult) { 120 | toast("Geofence added, success: " + addGeofenceResult.isSuccess()); 121 | } 122 | }, new Consumer() { 123 | @Override 124 | public void accept(Throwable throwable) { 125 | toast("Error adding geofence."); 126 | Log.d(TAG, "Error adding geofence.", throwable); 127 | } 128 | }); 129 | } 130 | 131 | private GeofencingRequest createGeofencingRequest() { 132 | try { 133 | double longitude = Double.parseDouble(longitudeInput.getText().toString()); 134 | double latitude = Double.parseDouble(latitudeInput.getText().toString()); 135 | float radius = Float.parseFloat(radiusInput.getText().toString()); 136 | Geofence geofence = new Geofence.Builder() 137 | .setRequestId("GEOFENCE") 138 | .setCircularRegion(latitude, longitude, radius) 139 | .setExpirationDuration(Geofence.NEVER_EXPIRE) 140 | .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT) 141 | .build(); 142 | return new GeofencingRequest.Builder().addGeofence(geofence).build(); 143 | } catch (NumberFormatException ex) { 144 | toast("Error parsing input."); 145 | return null; 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/GeofenceBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationManager; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.support.v4.app.NotificationCompat; 9 | 10 | import com.google.android.gms.location.Geofence; 11 | import com.google.android.gms.location.GeofencingEvent; 12 | 13 | public class GeofenceBroadcastReceiver extends BroadcastReceiver { 14 | @Override 15 | public void onReceive(Context context, Intent intent) { 16 | GeofencingEvent event = GeofencingEvent.fromIntent(intent); 17 | String transition = mapTransition(event.getGeofenceTransition()); 18 | 19 | NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 20 | Notification notification = new NotificationCompat.Builder(context) 21 | .setSmallIcon(R.drawable.ic_launcher) 22 | .setContentTitle("Geofence action") 23 | .setContentText(transition) 24 | .setTicker("Geofence action") 25 | .build(); 26 | nm.notify(0, notification); 27 | } 28 | 29 | private String mapTransition(int event) { 30 | switch (event) { 31 | case Geofence.GEOFENCE_TRANSITION_ENTER: 32 | return "ENTER"; 33 | case Geofence.GEOFENCE_TRANSITION_EXIT: 34 | return "EXIT"; 35 | default: 36 | return "UNKNOWN"; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample; 2 | 3 | import android.content.Intent; 4 | import android.content.IntentSender; 5 | import android.location.Address; 6 | import android.location.Location; 7 | import android.os.Bundle; 8 | import android.text.TextUtils; 9 | import android.util.Log; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.widget.TextView; 13 | import android.widget.Toast; 14 | 15 | import com.google.android.gms.common.api.Status; 16 | import com.google.android.gms.location.ActivityRecognitionResult; 17 | import com.google.android.gms.location.LocationRequest; 18 | import com.google.android.gms.location.LocationSettingsRequest; 19 | import com.google.android.gms.location.LocationSettingsResult; 20 | import com.google.android.gms.location.LocationSettingsStates; 21 | import com.google.android.gms.location.LocationSettingsStatusCodes; 22 | 23 | import java.util.List; 24 | 25 | import io.reactivex.Observable; 26 | import io.reactivex.android.schedulers.AndroidSchedulers; 27 | import io.reactivex.disposables.Disposable; 28 | import io.reactivex.functions.Consumer; 29 | import io.reactivex.functions.Function; 30 | import io.reactivex.schedulers.Schedulers; 31 | import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; 32 | import pl.charmas.android.reactivelocation2.ReactiveLocationProviderConfiguration; 33 | import pl.charmas.android.reactivelocation2.sample.utils.AddressToStringFunc; 34 | import pl.charmas.android.reactivelocation2.sample.utils.DetectedActivityToString; 35 | import pl.charmas.android.reactivelocation2.sample.utils.DisplayTextOnViewAction; 36 | import pl.charmas.android.reactivelocation2.sample.utils.LocationToStringFunc; 37 | import pl.charmas.android.reactivelocation2.sample.utils.ToMostProbableActivity; 38 | 39 | import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; 40 | 41 | public class MainActivity extends BaseActivity { 42 | private final static int REQUEST_CHECK_SETTINGS = 0; 43 | private final static String TAG = "MainActivity"; 44 | private ReactiveLocationProvider locationProvider; 45 | 46 | private TextView lastKnownLocationView; 47 | private TextView updatableLocationView; 48 | private TextView addressLocationView; 49 | private TextView currentActivityView; 50 | 51 | private Observable lastKnownLocationObservable; 52 | private Observable locationUpdatesObservable; 53 | private Observable activityObservable; 54 | 55 | private Disposable lastKnownLocationDisposable; 56 | private Disposable updatableLocationDisposable; 57 | private Disposable addressDisposable; 58 | private Disposable activityDisposable; 59 | private Observable addressObservable; 60 | 61 | @Override 62 | protected void onCreate(Bundle savedInstanceState) { 63 | super.onCreate(savedInstanceState); 64 | setContentView(R.layout.activity_main); 65 | 66 | lastKnownLocationView = (TextView) findViewById(R.id.last_known_location_view); 67 | updatableLocationView = (TextView) findViewById(R.id.updated_location_view); 68 | addressLocationView = (TextView) findViewById(R.id.address_for_location_view); 69 | currentActivityView = (TextView) findViewById(R.id.activity_recent_view); 70 | 71 | locationProvider = new ReactiveLocationProvider(getApplicationContext(), ReactiveLocationProviderConfiguration 72 | .builder() 73 | .setRetryOnConnectionSuspended(true) 74 | .build() 75 | ); 76 | 77 | lastKnownLocationObservable = locationProvider 78 | .getLastKnownLocation() 79 | .observeOn(AndroidSchedulers.mainThread()); 80 | 81 | final LocationRequest locationRequest = LocationRequest.create() 82 | .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) 83 | .setNumUpdates(5) 84 | .setInterval(100); 85 | locationUpdatesObservable = locationProvider 86 | .checkLocationSettings( 87 | new LocationSettingsRequest.Builder() 88 | .addLocationRequest(locationRequest) 89 | .setAlwaysShow(true) //Refrence: http://stackoverflow.com/questions/29824408/google-play-services-locationservices-api-new-option-never 90 | .build() 91 | ) 92 | .doOnNext(new Consumer() { 93 | @Override 94 | public void accept(LocationSettingsResult locationSettingsResult) { 95 | Status status = locationSettingsResult.getStatus(); 96 | if (status.getStatusCode() == LocationSettingsStatusCodes.RESOLUTION_REQUIRED) { 97 | try { 98 | status.startResolutionForResult(MainActivity.this, REQUEST_CHECK_SETTINGS); 99 | } catch (IntentSender.SendIntentException th) { 100 | Log.e("MainActivity", "Error opening settings activity.", th); 101 | } 102 | } 103 | } 104 | }) 105 | .flatMap(new Function>() { 106 | @Override 107 | public Observable apply(LocationSettingsResult locationSettingsResult) { 108 | return locationProvider.getUpdatedLocation(locationRequest); 109 | } 110 | }) 111 | .observeOn(AndroidSchedulers.mainThread()); 112 | 113 | addressObservable = locationProvider.getUpdatedLocation(locationRequest) 114 | .flatMap(new Function>>() { 115 | @Override 116 | public Observable> apply(Location location) { 117 | return locationProvider.getReverseGeocodeObservable(location.getLatitude(), location.getLongitude(), 1); 118 | } 119 | }) 120 | .map(new Function, Address>() { 121 | @Override 122 | public Address apply(List
addresses) { 123 | return addresses != null && !addresses.isEmpty() ? addresses.get(0) : null; 124 | } 125 | }) 126 | .map(new AddressToStringFunc()) 127 | .subscribeOn(Schedulers.io()) 128 | .observeOn(AndroidSchedulers.mainThread()); 129 | 130 | activityObservable = locationProvider 131 | .getDetectedActivity(50) 132 | .observeOn(AndroidSchedulers.mainThread()); 133 | } 134 | 135 | @Override 136 | protected void onLocationPermissionGranted() { 137 | lastKnownLocationDisposable = lastKnownLocationObservable 138 | .map(new LocationToStringFunc()) 139 | .subscribe(new DisplayTextOnViewAction(lastKnownLocationView), new ErrorHandler()); 140 | 141 | updatableLocationDisposable = locationUpdatesObservable 142 | .map(new LocationToStringFunc()) 143 | .map(new Function() { 144 | int count = 0; 145 | 146 | @Override 147 | public String apply(String s) { 148 | return s + " " + count++; 149 | } 150 | }) 151 | .subscribe(new DisplayTextOnViewAction(updatableLocationView), new ErrorHandler()); 152 | 153 | 154 | addressDisposable = addressObservable 155 | .subscribe(new DisplayTextOnViewAction(addressLocationView), new ErrorHandler()); 156 | 157 | activityDisposable = activityObservable 158 | .map(new ToMostProbableActivity()) 159 | .map(new DetectedActivityToString()) 160 | .subscribe(new DisplayTextOnViewAction(currentActivityView), new ErrorHandler()); 161 | } 162 | 163 | @Override 164 | protected void onStop() { 165 | super.onStop(); 166 | dispose(updatableLocationDisposable); 167 | dispose(addressDisposable); 168 | dispose(lastKnownLocationDisposable); 169 | dispose(activityDisposable); 170 | } 171 | 172 | @Override 173 | public boolean onCreateOptionsMenu(Menu menu) { 174 | menu.add("Geofencing").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { 175 | @Override 176 | public boolean onMenuItemClick(MenuItem item) { 177 | startActivity(new Intent(MainActivity.this, GeofenceActivity.class)); 178 | return true; 179 | } 180 | }); 181 | menu.add("Places").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { 182 | @Override 183 | public boolean onMenuItemClick(MenuItem item) { 184 | if (TextUtils.isEmpty(getString(R.string.API_KEY))) { 185 | Toast.makeText(MainActivity.this, "First you need to configure your API Key - see README.md", Toast.LENGTH_SHORT).show(); 186 | } else { 187 | startActivity(new Intent(MainActivity.this, PlacesActivity.class)); 188 | } 189 | return true; 190 | } 191 | }); 192 | menu.add("Mock Locations").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { 193 | @Override 194 | public boolean onMenuItemClick(MenuItem item) { 195 | startActivity(new Intent(MainActivity.this, MockLocationsActivity.class)); 196 | return true; 197 | } 198 | }); 199 | return true; 200 | } 201 | 202 | private class ErrorHandler implements Consumer { 203 | @Override 204 | public void accept(Throwable throwable) { 205 | Toast.makeText(MainActivity.this, "Error occurred.", Toast.LENGTH_SHORT).show(); 206 | Log.d("MainActivity", "Error occurred", throwable); 207 | } 208 | } 209 | 210 | @Override 211 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 212 | final LocationSettingsStates states = LocationSettingsStates.fromIntent(data);//intent); 213 | switch (requestCode) { 214 | case REQUEST_CHECK_SETTINGS: 215 | //Reference: https://developers.google.com/android/reference/com/google/android/gms/location/SettingsApi 216 | switch (resultCode) { 217 | case RESULT_OK: 218 | // All required changes were successfully made 219 | Log.d(TAG, "User enabled location"); 220 | break; 221 | case RESULT_CANCELED: 222 | // The user was asked to change settings, but chose not to 223 | Log.d(TAG, "User Cancelled enabling location"); 224 | break; 225 | default: 226 | break; 227 | } 228 | break; 229 | } 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/MockLocationsActivity.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample; 2 | 3 | import android.Manifest; 4 | import android.content.pm.PackageManager; 5 | import android.location.Location; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.os.SystemClock; 9 | import android.support.v4.app.ActivityCompat; 10 | import android.util.Log; 11 | import android.view.View; 12 | import android.widget.Button; 13 | import android.widget.CompoundButton; 14 | import android.widget.EditText; 15 | import android.widget.TextView; 16 | import android.widget.Toast; 17 | import android.widget.ToggleButton; 18 | 19 | import com.google.android.gms.common.api.Status; 20 | import com.google.android.gms.location.LocationRequest; 21 | 22 | import java.util.Date; 23 | 24 | import io.reactivex.Observable; 25 | import io.reactivex.disposables.Disposable; 26 | import io.reactivex.functions.BiFunction; 27 | import io.reactivex.functions.Consumer; 28 | import io.reactivex.functions.Function; 29 | import io.reactivex.subjects.PublishSubject; 30 | import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; 31 | import pl.charmas.android.reactivelocation2.sample.utils.DisplayTextOnViewAction; 32 | import pl.charmas.android.reactivelocation2.sample.utils.LocationToStringFunc; 33 | 34 | import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; 35 | 36 | public class MockLocationsActivity extends BaseActivity { 37 | private static final String TAG = "MockLocationsActivity"; 38 | 39 | private EditText latitudeInput; 40 | private EditText longitudeInput; 41 | private TextView mockLocationView; 42 | private TextView updatedLocationView; 43 | private ToggleButton mockModeToggleButton; 44 | private Button setLocationButton; 45 | 46 | private ReactiveLocationProvider locationProvider; 47 | private Observable mockLocationObservable; 48 | private Disposable mockLocationDisposable; 49 | private Disposable updatedLocationDisposable; 50 | 51 | private PublishSubject mockLocationSubject; 52 | 53 | @Override 54 | protected void onCreate(Bundle savedInstanceState) { 55 | super.onCreate(savedInstanceState); 56 | setContentView(R.layout.activity_mocklocations); 57 | 58 | locationProvider = new ReactiveLocationProvider(this); 59 | mockLocationSubject = PublishSubject.create(); 60 | 61 | mockLocationObservable = mockLocationSubject.hide(); 62 | 63 | initViews(); 64 | } 65 | 66 | private void initViews() { 67 | latitudeInput = (EditText) findViewById(R.id.latitude_input); 68 | longitudeInput = (EditText) findViewById(R.id.longitude_input); 69 | mockLocationView = (TextView) findViewById(R.id.mock_location_view); 70 | updatedLocationView = (TextView) findViewById(R.id.updated_location_view); 71 | mockModeToggleButton = (ToggleButton) findViewById(R.id.toggle_button); 72 | setLocationButton = (Button) findViewById(R.id.set_location_button); 73 | 74 | mockModeToggleButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { 75 | @Override 76 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 77 | setMockMode(isChecked); 78 | setLocationButton.setEnabled(isChecked); 79 | } 80 | }); 81 | setLocationButton.setOnClickListener(new View.OnClickListener() { 82 | @Override 83 | public void onClick(View v) { 84 | addMockLocation(); 85 | } 86 | }); 87 | } 88 | 89 | @Override 90 | protected void onLocationPermissionGranted() { 91 | mockModeToggleButton.setChecked(true); 92 | 93 | final LocationRequest locationRequest = LocationRequest.create() 94 | .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) 95 | .setInterval(2000); 96 | 97 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { 98 | return; 99 | } 100 | 101 | updatedLocationDisposable = locationProvider 102 | .getUpdatedLocation(locationRequest) 103 | .map(new LocationToStringFunc()) 104 | .map(new Function() { 105 | int count = 0; 106 | 107 | @Override 108 | public String apply(String s) { 109 | return s + " " + count++; 110 | } 111 | }) 112 | .subscribe(new DisplayTextOnViewAction(updatedLocationView)); 113 | } 114 | 115 | private void addMockLocation() { 116 | try { 117 | mockLocationSubject.onNext(createMockLocation()); 118 | } catch (Throwable e) { 119 | Toast.makeText(MockLocationsActivity.this, "Error parsing input.", Toast.LENGTH_SHORT).show(); 120 | } 121 | } 122 | 123 | private void setMockMode(boolean toggle) { 124 | if (toggle) { 125 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { 126 | return; 127 | } 128 | 129 | mockLocationDisposable = 130 | Observable.zip(locationProvider.mockLocation(mockLocationObservable), 131 | mockLocationObservable, new BiFunction() { 132 | int count = 0; 133 | 134 | @Override 135 | public String apply(Status result, Location location) { 136 | return new LocationToStringFunc().apply(location) + " " + count++; 137 | } 138 | }) 139 | .subscribe(new DisplayTextOnViewAction(mockLocationView), new ErrorHandler()); 140 | } else { 141 | mockLocationDisposable.dispose(); 142 | } 143 | } 144 | 145 | private Location createMockLocation() { 146 | String longitudeString = longitudeInput.getText().toString(); 147 | String latitudeString = latitudeInput.getText().toString(); 148 | 149 | if (!longitudeString.isEmpty() && !latitudeString.isEmpty()) { 150 | double longitude = Location.convert(longitudeString); 151 | double latitude = Location.convert(latitudeString); 152 | 153 | Location mockLocation = new Location("flp"); 154 | mockLocation.setLatitude(latitude); 155 | mockLocation.setLongitude(longitude); 156 | mockLocation.setAccuracy(1.0f); 157 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 158 | mockLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); 159 | } 160 | mockLocation.setTime(new Date().getTime()); 161 | return mockLocation; 162 | } else { 163 | throw new IllegalArgumentException(); 164 | } 165 | } 166 | 167 | @Override 168 | protected void onStop() { 169 | super.onStop(); 170 | dispose(mockLocationDisposable); 171 | dispose(updatedLocationDisposable); 172 | } 173 | 174 | private class ErrorHandler implements Consumer { 175 | @Override 176 | public void accept(Throwable throwable) { 177 | if (throwable instanceof SecurityException) { 178 | Toast.makeText(MockLocationsActivity.this, "You need to enable mock locations in Developer Options.", Toast.LENGTH_SHORT).show(); 179 | } else { 180 | Toast.makeText(MockLocationsActivity.this, "Error occurred.", Toast.LENGTH_SHORT).show(); 181 | Log.d(TAG, "Error occurred", throwable); 182 | } 183 | } 184 | } 185 | 186 | } 187 | 188 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesActivity.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample; 2 | 3 | import android.location.Location; 4 | import android.os.Bundle; 5 | import android.text.TextUtils; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.widget.AdapterView; 9 | import android.widget.ArrayAdapter; 10 | import android.widget.EditText; 11 | import android.widget.ListView; 12 | import android.widget.TextView; 13 | 14 | import com.google.android.gms.location.places.AutocompletePrediction; 15 | import com.google.android.gms.location.places.AutocompletePredictionBuffer; 16 | import com.google.android.gms.location.places.PlaceLikelihood; 17 | import com.google.android.gms.location.places.PlaceLikelihoodBuffer; 18 | import com.google.android.gms.maps.model.LatLng; 19 | import com.google.android.gms.maps.model.LatLngBounds; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.concurrent.TimeUnit; 24 | 25 | import io.reactivex.Observable; 26 | import io.reactivex.disposables.CompositeDisposable; 27 | import io.reactivex.functions.BiFunction; 28 | import io.reactivex.functions.Consumer; 29 | import io.reactivex.functions.Function; 30 | import io.reactivex.functions.Predicate; 31 | import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; 32 | import pl.charmas.android.reactivelocation2.sample.utils.RxTextView; 33 | 34 | import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; 35 | 36 | public class PlacesActivity extends BaseActivity { 37 | 38 | private TextView currentPlaceView; 39 | private EditText queryView; 40 | private ListView placeSuggestionsList; 41 | private ReactiveLocationProvider reactiveLocationProvider; 42 | private CompositeDisposable compositeDisposable; 43 | 44 | @Override 45 | protected void onCreate(Bundle savedInstanceState) { 46 | super.onCreate(savedInstanceState); 47 | setContentView(R.layout.activity_places); 48 | currentPlaceView = (TextView) findViewById(R.id.current_place_view); 49 | queryView = (EditText) findViewById(R.id.place_query_view); 50 | placeSuggestionsList = (ListView) findViewById(R.id.place_suggestions_list); 51 | placeSuggestionsList.setOnItemClickListener(new AdapterView.OnItemClickListener() { 52 | @Override 53 | public void onItemClick(AdapterView parent, View view, int position, long id) { 54 | AutocompleteInfo info = (AutocompleteInfo) parent.getAdapter().getItem(position); 55 | startActivity(PlacesResultActivity.getStartIntent(PlacesActivity.this, info.id)); 56 | } 57 | }); 58 | 59 | reactiveLocationProvider = new ReactiveLocationProvider(this); 60 | } 61 | 62 | @Override 63 | protected void onLocationPermissionGranted() { 64 | compositeDisposable = new CompositeDisposable(); 65 | compositeDisposable.add( 66 | reactiveLocationProvider.getCurrentPlace(null) 67 | .subscribe(new Consumer() { 68 | @Override 69 | public void accept(PlaceLikelihoodBuffer buffer) { 70 | PlaceLikelihood likelihood = buffer.get(0); 71 | if (likelihood != null) { 72 | currentPlaceView.setText(likelihood.getPlace().getName()); 73 | } 74 | buffer.release(); 75 | } 76 | }, new Consumer() { 77 | @Override 78 | public void accept(Throwable throwable) throws Exception { 79 | Log.e("PlacesActivity", "Error in observable", throwable); 80 | } 81 | }) 82 | ); 83 | 84 | Observable queryObservable = RxTextView 85 | .textChanges(queryView) 86 | .map(new Function() { 87 | @Override 88 | public String apply(CharSequence charSequence) { 89 | return charSequence.toString(); 90 | } 91 | }) 92 | .debounce(1, TimeUnit.SECONDS) 93 | .filter(new Predicate() { 94 | @Override 95 | public boolean test(String s) { 96 | return !TextUtils.isEmpty(s); 97 | } 98 | }); 99 | Observable lastKnownLocationObservable = reactiveLocationProvider.getLastKnownLocation(); 100 | Observable suggestionsObservable = Observable 101 | .combineLatest(queryObservable, lastKnownLocationObservable, 102 | new BiFunction() { 103 | @Override 104 | public QueryWithCurrentLocation apply(String query, Location currentLocation) { 105 | return new QueryWithCurrentLocation(query, currentLocation); 106 | } 107 | }).flatMap(new Function>() { 108 | @Override 109 | public Observable apply(QueryWithCurrentLocation q) { 110 | if (q.location == null) return Observable.empty(); 111 | 112 | double latitude = q.location.getLatitude(); 113 | double longitude = q.location.getLongitude(); 114 | LatLngBounds bounds = new LatLngBounds( 115 | new LatLng(latitude - 0.05, longitude - 0.05), 116 | new LatLng(latitude + 0.05, longitude + 0.05) 117 | ); 118 | return reactiveLocationProvider.getPlaceAutocompletePredictions(q.query, bounds, null); 119 | } 120 | }); 121 | 122 | compositeDisposable.add(suggestionsObservable.subscribe(new Consumer() { 123 | @Override 124 | public void accept(AutocompletePredictionBuffer buffer) { 125 | List infos = new ArrayList<>(); 126 | for (AutocompletePrediction prediction : buffer) { 127 | infos.add(new AutocompleteInfo(prediction.getFullText(null).toString(), prediction.getPlaceId())); 128 | } 129 | buffer.release(); 130 | placeSuggestionsList.setAdapter(new ArrayAdapter<>(PlacesActivity.this, android.R.layout.simple_list_item_1, infos)); 131 | } 132 | })); 133 | } 134 | 135 | @Override 136 | protected void onStop() { 137 | super.onStop(); 138 | dispose(compositeDisposable); 139 | } 140 | 141 | private static class QueryWithCurrentLocation { 142 | final String query; 143 | public final Location location; 144 | 145 | private QueryWithCurrentLocation(String query, Location location) { 146 | this.query = query; 147 | this.location = location; 148 | } 149 | } 150 | 151 | private static class AutocompleteInfo { 152 | private final String description; 153 | private final String id; 154 | 155 | private AutocompleteInfo(String description, String id) { 156 | this.description = description; 157 | this.id = id; 158 | } 159 | 160 | @Override 161 | public String toString() { 162 | return description; 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/PlacesResultActivity.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.annotation.NonNull; 7 | import android.widget.TextView; 8 | 9 | import com.google.android.gms.location.places.Place; 10 | import com.google.android.gms.location.places.PlaceBuffer; 11 | 12 | import io.reactivex.disposables.CompositeDisposable; 13 | import io.reactivex.functions.Consumer; 14 | import pl.charmas.android.reactivelocation2.ReactiveLocationProvider; 15 | 16 | import static pl.charmas.android.reactivelocation2.sample.utils.UnsubscribeIfPresent.dispose; 17 | 18 | public class PlacesResultActivity extends BaseActivity { 19 | 20 | private static final String EXTRA_PLACE_ID = "EXTRA_PLACE_ID"; 21 | 22 | private ReactiveLocationProvider reactiveLocationProvider; 23 | private CompositeDisposable compositeSubscription; 24 | private TextView placeNameView; 25 | private TextView placeLocationView; 26 | private TextView placeAddressView; 27 | private String placeId; 28 | 29 | public static Intent getStartIntent(Context context, @NonNull String placeId) { 30 | Intent startIntent = new Intent(context, PlacesResultActivity.class); 31 | startIntent.putExtra(EXTRA_PLACE_ID, placeId); 32 | 33 | return startIntent; 34 | } 35 | 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | setContentView(R.layout.activity_places_result); 40 | 41 | placeNameView = (TextView) findViewById(R.id.place_name_view); 42 | placeLocationView = (TextView) findViewById(R.id.place_location_view); 43 | placeAddressView = (TextView) findViewById(R.id.place_address_view); 44 | 45 | reactiveLocationProvider = new ReactiveLocationProvider(this); 46 | 47 | getPlaceIdFromIntent(); 48 | } 49 | 50 | private void getPlaceIdFromIntent() { 51 | Intent loadedIntent = getIntent(); 52 | placeId = loadedIntent.getStringExtra(EXTRA_PLACE_ID); 53 | 54 | if (placeId == null) { 55 | throw new IllegalStateException("You must start SearchResultsActivity with a non-null place Id using getStartIntent(Context, String)"); 56 | } 57 | } 58 | 59 | @Override 60 | protected void onLocationPermissionGranted() { 61 | compositeSubscription = new CompositeDisposable(); 62 | compositeSubscription.add(reactiveLocationProvider.getPlaceById(placeId) 63 | .subscribe(new Consumer() { 64 | @Override 65 | public void accept(PlaceBuffer buffer) { 66 | Place place = buffer.get(0); 67 | if (place != null) { 68 | placeNameView.setText(place.getName()); 69 | placeLocationView.setText(place.getLatLng().latitude + ", " + place.getLatLng().longitude); 70 | placeAddressView.setText(place.getAddress()); 71 | } 72 | buffer.release(); 73 | } 74 | })); 75 | } 76 | 77 | @Override 78 | protected void onStop() { 79 | super.onStop(); 80 | dispose(compositeSubscription); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/AddressToStringFunc.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample.utils; 2 | 3 | import android.location.Address; 4 | 5 | import io.reactivex.functions.Function; 6 | 7 | public class AddressToStringFunc implements Function { 8 | @Override 9 | public String apply(Address address) { 10 | if (address == null) return ""; 11 | 12 | String addressLines = ""; 13 | for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) { 14 | addressLines += address.getAddressLine(i) + '\n'; 15 | } 16 | return addressLines; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/DetectedActivityToString.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample.utils; 2 | 3 | import com.google.android.gms.location.DetectedActivity; 4 | 5 | import io.reactivex.functions.Function; 6 | 7 | public class DetectedActivityToString implements Function { 8 | @Override 9 | public String apply(DetectedActivity detectedActivity) { 10 | return getNameFromType(detectedActivity.getType()) + " with confidence " + detectedActivity.getConfidence(); 11 | } 12 | 13 | private String getNameFromType(int activityType) { 14 | switch (activityType) { 15 | case DetectedActivity.RUNNING: 16 | return "running"; 17 | case DetectedActivity.IN_VEHICLE: 18 | return "in_vehicle"; 19 | case DetectedActivity.ON_BICYCLE: 20 | return "on_bicycle"; 21 | case DetectedActivity.ON_FOOT: 22 | return "on_foot"; 23 | case DetectedActivity.STILL: 24 | return "still"; 25 | case DetectedActivity.UNKNOWN: 26 | return "unknown"; 27 | case DetectedActivity.TILTING: 28 | return "tilting"; 29 | } 30 | return "unknown"; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/DisplayTextOnViewAction.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample.utils; 2 | 3 | import android.widget.TextView; 4 | 5 | import io.reactivex.functions.Consumer; 6 | 7 | public class DisplayTextOnViewAction implements Consumer { 8 | private final TextView target; 9 | 10 | public DisplayTextOnViewAction(TextView target) { 11 | this.target = target; 12 | } 13 | 14 | @Override 15 | public void accept(String s) throws Exception { 16 | target.setText(s); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/LocationToStringFunc.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample.utils; 2 | 3 | import android.location.Location; 4 | 5 | import io.reactivex.functions.Function; 6 | 7 | public class LocationToStringFunc implements Function { 8 | @Override 9 | public String apply(Location location) { 10 | if (location != null) 11 | return location.getLatitude() + " " + location.getLongitude() + " (" + location.getAccuracy() + ")"; 12 | return "no location available"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/RxTextView.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample.utils; 2 | 3 | import android.support.annotation.CheckResult; 4 | import android.support.annotation.NonNull; 5 | import android.widget.TextView; 6 | 7 | import io.reactivex.Observable; 8 | 9 | 10 | /** 11 | * RxTextView compatible to rxjava2 12 | */ 13 | 14 | public class RxTextView { 15 | 16 | /** 17 | * Create an observable of character sequences for text changes on {@code view}. 18 | *

19 | * Warning: Values emitted by this observable are mutable and owned by the host 20 | * {@code TextView} and thus are not safe to cache or delay reading (such as by observing 21 | * on a different thread). If you want to cache or delay reading the items emitted then you must 22 | * map values through a function which calls {@link String#valueOf} or 23 | * {@link CharSequence#toString() .toString()} to create a copy. 24 | *

25 | * Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe 26 | * to free this reference. 27 | *

28 | * Note: A value will be emitted immediately on subscribe. 29 | */ 30 | @CheckResult 31 | @NonNull 32 | public static Observable textChanges(@NonNull TextView view) { 33 | return Observable.create(new TextViewTextOnSubscribe(view)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/TextViewTextOnSubscribe.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample.utils; 2 | 3 | import android.os.Looper; 4 | import android.text.Editable; 5 | import android.text.TextWatcher; 6 | import android.widget.TextView; 7 | 8 | import java.util.concurrent.atomic.AtomicBoolean; 9 | 10 | import io.reactivex.ObservableEmitter; 11 | import io.reactivex.ObservableOnSubscribe; 12 | import io.reactivex.android.schedulers.AndroidSchedulers; 13 | import io.reactivex.disposables.Disposable; 14 | 15 | class TextViewTextOnSubscribe implements ObservableOnSubscribe { 16 | private final TextView view; 17 | 18 | TextViewTextOnSubscribe(TextView view) { 19 | this.view = view; 20 | } 21 | 22 | @Override 23 | public void subscribe(final ObservableEmitter emitter) throws Exception { 24 | 25 | final TextWatcher watcher = new TextWatcher() { 26 | @Override 27 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 28 | } 29 | 30 | @Override 31 | public void onTextChanged(CharSequence s, int start, int before, int count) { 32 | if (!emitter.isDisposed()) { 33 | emitter.onNext(s); 34 | } 35 | } 36 | 37 | @Override 38 | public void afterTextChanged(Editable s) { 39 | } 40 | }; 41 | 42 | emitter.setDisposable(new MainThreadDisposable() { 43 | @Override 44 | protected void onDisposed() { 45 | view.removeTextChangedListener(watcher); 46 | } 47 | }); 48 | 49 | view.addTextChangedListener(watcher); 50 | 51 | // Emit initial value. 52 | emitter.onNext(view.getText()); 53 | } 54 | 55 | abstract class MainThreadDisposable implements Disposable { 56 | 57 | private final AtomicBoolean dispose = new AtomicBoolean(); 58 | 59 | @Override 60 | public void dispose() { 61 | if (dispose.compareAndSet(false, true)) { 62 | if (Looper.myLooper() == Looper.getMainLooper()) { 63 | onDisposed(); 64 | } else { 65 | AndroidSchedulers.mainThread() 66 | .createWorker() 67 | .schedule(new Runnable() { 68 | @Override 69 | public void run() { 70 | onDisposed(); 71 | } 72 | }); 73 | } 74 | } 75 | } 76 | 77 | @Override 78 | public boolean isDisposed() { 79 | return dispose.get(); 80 | } 81 | 82 | abstract void onDisposed(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/ToMostProbableActivity.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample.utils; 2 | 3 | import com.google.android.gms.location.ActivityRecognitionResult; 4 | import com.google.android.gms.location.DetectedActivity; 5 | 6 | import io.reactivex.functions.Function; 7 | 8 | public class ToMostProbableActivity implements Function { 9 | @Override 10 | public DetectedActivity apply(ActivityRecognitionResult activityRecognitionResult) { 11 | return activityRecognitionResult.getMostProbableActivity(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /sample/src/main/java/pl/charmas/android/reactivelocation2/sample/utils/UnsubscribeIfPresent.java: -------------------------------------------------------------------------------- 1 | package pl.charmas.android.reactivelocation2.sample.utils; 2 | 3 | import io.reactivex.disposables.Disposable; 4 | 5 | public final class UnsubscribeIfPresent { 6 | private UnsubscribeIfPresent() {//no instance 7 | } 8 | 9 | public static void dispose(Disposable disposable) { 10 | if (disposable != null && !disposable.isDisposed()) { 11 | disposable.dispose(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcharmas/Android-ReactiveLocation/8dce277e49540767965b70502567af93593de994/sample/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcharmas/Android-ReactiveLocation/8dce277e49540767965b70502567af93593de994/sample/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcharmas/Android-ReactiveLocation/8dce277e49540767965b70502567af93593de994/sample/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mcharmas/Android-ReactiveLocation/8dce277e49540767965b70502567af93593de994/sample/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_geofence.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 20 | 21 | 27 | 28 | 35 | 36 | 43 | 44 | 51 | 52 | 58 | 59 |