Repository: patloew/RxLocation Branch: master Commit: 21e73f32215f Files: 84 Total size: 217.4 KB Directory structure: gitextract_ql1kc9k6/ ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── NOTICE ├── README.md ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── library/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules-consumer.pro │ ├── proguard-rules.pro │ └── src/ │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── patloew/ │ │ │ └── rxlocation/ │ │ │ ├── ActivityRecognition.java │ │ │ ├── ActivityRemoveUpdatesSingleOnSubscribe.java │ │ │ ├── ActivityRequestUpdatesSingleOnSubscribe.java │ │ │ ├── FusedLocation.java │ │ │ ├── Geocoding.java │ │ │ ├── Geofencing.java │ │ │ ├── GeofencingAddSingleOnSubscribe.java │ │ │ ├── GeofencingRemoveSingleOnSubscribe.java │ │ │ ├── GoogleApiClientFlowable.java │ │ │ ├── GoogleApiConnectionException.java │ │ │ ├── GoogleApiConnectionSuspendedException.java │ │ │ ├── LocationAvailabilitySingleOnSubscribe.java │ │ │ ├── LocationFlushSingleOnSubscribe.java │ │ │ ├── LocationLastMaybeOnSubscribe.java │ │ │ ├── LocationRemoveUpdatesSingleOnSubscribe.java │ │ │ ├── LocationRequestUpdatesSingleOnSubscribe.java │ │ │ ├── LocationSettings.java │ │ │ ├── LocationSettingsActivity.java │ │ │ ├── LocationSettingsNotSatisfiedException.java │ │ │ ├── LocationUpdatesFlowableOnSubscribe.java │ │ │ ├── RxLocation.java │ │ │ ├── RxLocationBaseOnSubscribe.java │ │ │ ├── RxLocationFlowableOnSubscribe.java │ │ │ ├── RxLocationMaybeOnSubscribe.java │ │ │ ├── RxLocationSingleOnSubscribe.java │ │ │ ├── SettingsCheckHandleSingleOnSubscribe.java │ │ │ ├── SettingsCheckSingleOnSubscribe.java │ │ │ ├── SingleResultCallBack.java │ │ │ ├── StatusErrorResultCallBack.java │ │ │ ├── StatusException.java │ │ │ └── StatusExceptionResumeNextTransformer.java │ │ └── res/ │ │ └── values/ │ │ └── strings.xml │ └── test/ │ └── java/ │ └── com/ │ └── patloew/ │ └── rxlocation/ │ ├── ActivityOnSubscribeTest.java │ ├── ActivityTest.java │ ├── BaseOnSubscribeTest.java │ ├── BaseTest.java │ ├── GeocodingTest.java │ ├── GeofencingOnSubscribeTest.java │ ├── GeofencingTest.java │ ├── LocationOnSubscribeTest.java │ ├── LocationSettingsActivityTest.java │ ├── LocationTest.java │ ├── RxLocationBaseOnSubscribeTest.java │ ├── RxLocationFlowableOnSubscribeTest.java │ ├── RxLocationMaybeOnSubscribeTest.java │ ├── RxLocationSingleOnSubscribeTest.java │ ├── RxLocationTest.java │ ├── SettingsOnSubscribeTest.java │ └── SettingsTest.java ├── sample/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── patloew/ │ │ │ └── rxlocationsample/ │ │ │ ├── MainActivity.java │ │ │ ├── MainPresenter.java │ │ │ ├── MainView.java │ │ │ └── MyApplication.java │ │ └── res/ │ │ ├── layout/ │ │ │ └── activity_main.xml │ │ ├── menu/ │ │ │ └── menu.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── values-w820dp/ │ │ └── dimens.xml │ └── test/ │ └── java/ │ └── com/ │ └── patloew/ │ └── rxlocationsample/ │ ├── MainPresenterTest.java │ └── RxSchedulersOverrideRule.java └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.iml .gradle /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures .idea ================================================ FILE: .travis.yml ================================================ language: android jdk: - oraclejdk8 android: components: # Uncomment the lines below if you want to # use the latest revision of Android SDK Tools - platform-tools - tools # The BuildTools version used by your project - build-tools-28.0.0 # The SDK version used to compile your project - android-28 # Additional components - extra-google-google_play_services - extra-google-m2repository - extra-android-m2repository before_install: - export JAVA8_HOME=/usr/lib/jvm/java-8-oracle - export JAVA_HOME=$JAVA8_HOME after_success: - bash <(curl -s https://codecov.io/bash) before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock script: - ./gradlew build jacocoTestReport assembleAndroidTest - ./gradlew connectedCheck cache: directories: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## Version 1.0.5 * Update dependencies * Fix bugs #55, #38 ## Version 1.0.4 * Update dependencies * BREAKING: Removed Retrolambda. Since this library uses Java 8 features, from v1.0.4 on, only Android gradle plugin 3.0.0 or higher is supported. ## Version 1.0.3 * Update dependencies (Play Services 10.2.1) * Fix bug which lead to leaks when using `rxLocation.location().updates() (#26) * BREAKING: Removed `GoogleAPIClientSingle` and replaced it with `GoogleApiClientFlowable` (#22) * BREAKING: Replace uppercase "API" in `GoogleApiConnectionException` and `GoogleApiConnectionSuspendedException` with "Api" ## Version 1.0.2 * Update dependencies (RxJava 2.0.7, Play Services 10.2.0) * Fix bug which made activity recognition API unusable (#24) * Add `StatusExceptionResumeNextTransformer` to help with handlig the resolution of a `Result` yourself (#17) ## Version 1.0.1 * Add consumer ProGuard rules. (#7, #8 – thanks Evisceration) ================================================ FILE: LICENSE ================================================ Copyright 2016 Patrick Löwenstein Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: NOTICE ================================================ RxLocation Copyright 2016 Patrick Löwenstein =========================== Apache License, Version 2.0 =========================== The following components are provided under the Apache License, Version 2.0. See project link for details. ------ RxJava ------ io.reactivex:rxjava https://github.com/ReactiveX/RxJava Copyright 2013 Netflix, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ------------------------ Android-ReactiveLocation ------------------------ pl.charmas.android:android-reactive-location https://github.com/mcharmas/Android-ReactiveLocation Copyright (C) 2015 Michał Charmas (http://blog.charmas.pl) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Reactive Location API Library for Android [![Build Status](https://travis-ci.org/patloew/RxLocation.svg?branch=master)](https://travis-ci.org/patloew/RxLocation) [![codecov](https://codecov.io/gh/patloew/RxLocation/branch/master/graph/badge.svg)](https://codecov.io/gh/patloew/RxLocation) [![Download](https://api.bintray.com/packages/patloew/maven/RxLocation/images/download.svg) ](https://bintray.com/patloew/maven/RxLocation/_latestVersion) [![API](https://img.shields.io/badge/API-14%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=14) **This library is now deprecated and not maintained anymore. Please switch to the [CoLocation](https://github.com/patloew/CoLocation) library.** This library wraps the Location APIs in [RxJava 2](https://github.com/ReactiveX/RxJava/tree/2.x) Observables, Singles, Maybes and Completables. No more managing GoogleApiClients! Also, the resolution of the location settings check is optionally handled by the lib. For [RxJava 1](https://github.com/ReactiveX/RxJava/tree/1.x), please take a look at the [Android-ReactiveLocation](https://github.com/mcharmas/Android-ReactiveLocation) library by Michał Charmas. # Usage Create an RxLocation instance once, preferably in your Application's `onCreate()` or by using a dependency injection framework. The RxLocation class is very similar to the classes provided by the Location APIs. Instead of `LocationServices.FusedLocationApi.getLastLocation(apiClient)` you can use `rxLocation.location().lastLocation()`. Make sure to have the Location and Activity Recognition permissions from Marshmallow on, if they are needed by your API requests. Example: ```java // Create one instance and share it RxLocation rxLocation = new RxLocation(context); LocationRequest locationRequest = LocationRequest.create() .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) .setInterval(5000); rxLocation.location().updates(locationRequest) .flatMap(location -> rxLocation.geocoding().fromLocation(location).toObservable()) .subscribe(address -> { /* do something */ }); ``` The following APIs are wrapped by this library: * `ActivityRecognition.ActivityRecognitionApi` via `rxLocation.activity()` * `LocationServices.FusedLocationApi` via `rxLocation.location()` * `LocationServices.GeofencingApi` via `rxLocation.geofencing()` * `LocationServices.SettingsApi` via `rxLocation.settings()` * `Geocoder` via `rxLocation.geocoding()` Checking the location settings is simplified with this library, by providing a `Single` via `rxLocation.settings().checkAndHandleResolution(locationRequest)`, which handles showing the resolution dialog if the location settings do not satisfy your request. It returns `true` if the settings are satisfied (optionally after showing the dialog, if a resolution is possible), and `false` otherwise. If you want to handle the `LocationSettingsResult` yourself, you can do so via `rxLocation.settings().check(locationRequest)`. An optional global default timeout for all Location API requests made through the library can be set via `rxLocation.setDefaultTimeout(...)`. In addition, timeouts can be set when creating a new Observable by providing timeout parameters, e.g. `rxLocation.geofencing().add(geofencingRequest, pendingIntent, 15, TimeUnit.SECONDS)`. These parameters override the default timeout. When a timeout occurs, a StatusException is provided via `onError()`. Keep in mind that these timeouts only apply to calls to the Location API, e.g. when registering a location update listener. As an example, the timeout provided to `rxLocation.location().updates(locationRequest, 15, TimeUnit.Seconds)` does *not* mean that you will not receive location updates anymore after 15 seconds. Use `setExpirationDuration()` on your locationRequest for this use case. Don't forget to dispose of your Subscriber/Observer when you are finished: ```java Disposable disposable = rxLocation.location().updates(locationRequest).subscribe(); // Dispose of your Observer when you no longer need updates disposable.dispose(); ``` As an alternative, multiple Disposables can be collected to dipose of at once via `CompositeDisposable`: ```java CompositeDisposable disposable = new CompositeDisposable(); disposable.add(rxLocation.location().updates(locationRequest).subscribe()); // Dispose of all collected Disposables at once, e.g. in onDestroy() disposable.clear(); ``` You can also obtain a `Flowable`, which connects on subscribe and disconnects on dispose via `GoogleApiClientFlowable.create(...)`. The following Exceptions are thrown in the lib and provided via `onError()`: * `StatusException`: When the call to the Location APIs was not successful or timed out * `GoogleApiConnectionException`: When connecting to the GoogleApiClient was not successful. * `GoogleApiConnectionSuspendedException`: When the GoogleApiClient connection was suspended. * `SecurityException`: When you try to call an API without proper permissions. * `LocationSettingsNotSatisfiedException`: When you use `rxLocation.settings().checkAndHandleResolutionCompletable(...)` and the location settings were not satisfied, even after handling the resolution. When using the Geocoding component of RxLocation, exceptions can be thrown even after disposing, which can lead to app crashes. To prevent this, install a global `RxJavaPlugins.setErrorHandler()`, e.g. in your Application class. # Sample A basic sample app is available in the `sample` project. # Setup The latest version 1.0.5 of this library is available on Maven Central. Add the following to your `build.gradle`: ```groovy dependencies { compile 'com.patloew.rxlocation:rxlocation:1.0.5' compile 'com.google.android.gms:play-services-location:15.0.0' } ``` If you want to use a newer version of Google Play Services, declare the newer version in your `build.gradle`. This then overrides the version declared in the library. From v1.0.4 on, RxLocation only works with Android gradle plugin 3.0.0 or higher, since it uses Java 8 language features. And don't forget to set the source code compatibility to Java 8: ```groovy android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } ``` # Testing When unit testing your app's classes, RxLocation behavior can be mocked easily. See the `MainPresenterTest` in the `sample` project for an example test. # Credits The code for managing the GoogleApiClient was adapted from the [Android-ReactiveLocation](https://github.com/mcharmas/Android-ReactiveLocation) library by Michał Charmas, which is licensed under the Apache License, Version 2.0. # Donations If you like the library and want to support the creator for development/maintenance, you can make a donation in Bitcoin to `bc1q5uejfyl2kskhhveg7lx4fcwgv8hz88r92yzjsu`. Thank you! # License Copyright 2016 Patrick Löwenstein Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:4.1.3' classpath 'com.vanniktech:gradle-maven-publish-plugin:0.14.2' classpath "com.vanniktech:gradle-android-junit-jacoco-plugin:0.16.0" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } apply plugin: "com.vanniktech.android.junit.jacoco" allprojects { repositories { google() mavenCentral() } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. # Default value: -Xmx10248m -XX:MaxPermSize=256m # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx2048m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true GROUP=com.patloew.rxlocation POM_ARTIFACT_ID=rxlocation VERSION_NAME=1.0.5 POM_NAME=rxlocation POM_PACKAGING=aar POM_DESCRIPTION=Reactive Location APIs Library for Android and RxJava 2 POM_INCEPTION_YEAR=2016 POM_URL=https://github.com/patloew/RxLocation POM_SCM_URL=https://github.com/patloew/RxLocation POM_SCM_CONNECTION=scm:git@github.com:patloew/RxLocation.git POM_SCM_DEV_CONNECTION=scm:git@github.com:patloew/RxLocation.git POM_LICENCE_NAME=The Apache Software License, Version 2.0 POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt POM_LICENCE_DIST=repo POM_DEVELOPER_ID=patloew POM_DEVELOPER_NAME=Patrick Lwenstein POM_DEVELOPER_URL=https://github.com/patloew ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=`expr $i + 1` done case $i in 0) set -- ;; 1) set -- "$args0" ;; 2) set -- "$args0" "$args1" ;; 3) set -- "$args0" "$args1" "$args2" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: library/.gitignore ================================================ /build ================================================ FILE: library/build.gradle ================================================ ext { RELEASE_REPOSITORY_URL = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" SNAPSHOT_REPOSITORY_URL = "https://s01.oss.sonatype.org/content/repositories/snapshots/" } apply plugin: 'com.android.library' apply plugin: 'com.vanniktech.maven.publish' android { compileSdkVersion 28 defaultConfig { minSdkVersion 14 targetSdkVersion 28 versionCode 5 versionName "1.0.5" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' consumerProguardFiles 'proguard-rules-consumer.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } testOptions { unitTests.returnDefaultValues = true } lintOptions { abortOnError false } } dependencies { api fileTree(dir: 'libs', include: ['*.jar']) api 'io.reactivex.rxjava2:rxjava:2.1.14' // Updating play services breaks unit tests api 'com.google.android.gms:play-services-location:10.2.1' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.18.3' testImplementation "org.powermock:powermock-module-junit4:2.0.0-beta.5" testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5" } task generateSourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs classifier 'sources' } task generateJavadocs(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) } task generateJavadocsJar(type: Jar) { from generateJavadocs.destinationDir classifier 'javadoc' } generateJavadocsJar.dependsOn generateJavadocs artifacts { archives generateJavadocsJar archives generateSourcesJar } ================================================ FILE: library/proguard-rules-consumer.pro ================================================ -dontwarn java.lang.invoke.* ================================================ FILE: library/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/patricklowenstein/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: library/src/main/AndroidManifest.xml ================================================ ================================================ FILE: library/src/main/java/com/patloew/rxlocation/ActivityRecognition.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import android.support.annotation.NonNull; import android.support.annotation.RequiresPermission; import com.google.android.gms.common.api.Status; import java.util.concurrent.TimeUnit; import io.reactivex.Single; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class ActivityRecognition { private final RxLocation rxLocation; ActivityRecognition(RxLocation rxLocation) { this.rxLocation = rxLocation; } // Request Updates @RequiresPermission("com.google.android.gms.permission.ACTIVITY_RECOGNITION") public Single requestUpdates(long detectionIntervalMillis, @NonNull PendingIntent pendingIntent) { return requestUpdatesInternal(detectionIntervalMillis, pendingIntent, null, null); } @RequiresPermission("com.google.android.gms.permission.ACTIVITY_RECOGNITION") public Single requestUpdates(long detectionIntervalMillis, @NonNull PendingIntent pendingIntent, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return requestUpdatesInternal(detectionIntervalMillis, pendingIntent, timeoutTime, timeoutUnit); } private Single requestUpdatesInternal(long detectionIntervalMillis, PendingIntent pendingIntent, Long timeout, TimeUnit timeUnit) { return Single.create(new ActivityRequestUpdatesSingleOnSubscribe(rxLocation, detectionIntervalMillis, pendingIntent, timeout, timeUnit)); } // Remove Updates @RequiresPermission("com.google.android.gms.permission.ACTIVITY_RECOGNITION") public Single removeUpdates(@NonNull PendingIntent pendingIntent) { return removeUpdatesInternal(pendingIntent, null, null); } @RequiresPermission("com.google.android.gms.permission.ACTIVITY_RECOGNITION") public Single removeUpdates(@NonNull PendingIntent pendingIntent, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return removeUpdatesInternal(pendingIntent, timeoutTime, timeoutUnit); } private Single removeUpdatesInternal(PendingIntent pendingIntent, Long timeout, TimeUnit timeUnit) { return Single.create(new ActivityRemoveUpdatesSingleOnSubscribe(rxLocation, pendingIntent, timeout, timeUnit)); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/ActivityRemoveUpdatesSingleOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import android.support.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.ActivityRecognition; import java.util.concurrent.TimeUnit; import io.reactivex.SingleEmitter; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class ActivityRemoveUpdatesSingleOnSubscribe extends RxLocationSingleOnSubscribe { final PendingIntent pendingIntent; ActivityRemoveUpdatesSingleOnSubscribe(@NonNull RxLocation rxLocation, PendingIntent pendingIntent, Long timeout, TimeUnit timeUnit) { super(rxLocation, timeout, timeUnit); this.pendingIntent = pendingIntent; } @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { //noinspection MissingPermission setupLocationPendingResult( ActivityRecognition.ActivityRecognitionApi.removeActivityUpdates(apiClient, pendingIntent), SingleResultCallBack.get(emitter) ); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/ActivityRequestUpdatesSingleOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import android.support.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.ActivityRecognition; import java.util.concurrent.TimeUnit; import io.reactivex.SingleEmitter; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class ActivityRequestUpdatesSingleOnSubscribe extends RxLocationSingleOnSubscribe { final long detectionIntervalMillis; final PendingIntent pendingIntent; ActivityRequestUpdatesSingleOnSubscribe(@NonNull RxLocation rxLocation, long detectionIntervalMillis, PendingIntent pendingIntent, Long timeout, TimeUnit timeUnit) { super(rxLocation, timeout, timeUnit); this.detectionIntervalMillis = detectionIntervalMillis; this.pendingIntent = pendingIntent; } @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { //noinspection MissingPermission setupLocationPendingResult( ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(apiClient, detectionIntervalMillis, pendingIntent), SingleResultCallBack.get(emitter) ); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/FusedLocation.java ================================================ package com.patloew.rxlocation; import android.Manifest; import android.app.PendingIntent; import android.location.Location; import android.os.Looper; import android.support.annotation.NonNull; import android.support.annotation.RequiresPermission; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationRequest; import java.util.concurrent.TimeUnit; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.Single; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class FusedLocation { private final RxLocation rxLocation; FusedLocation(RxLocation rxLocation) { this.rxLocation = rxLocation; } // Flush public Single flush() { return flushInternal(null, null); } public Single flush(long timeoutTime, @NonNull TimeUnit timeoutUnit) { return flushInternal(timeoutTime, timeoutUnit); } private Single flushInternal(Long timeoutTime, TimeUnit timeoutUnit) { return Single.create(new LocationFlushSingleOnSubscribe(rxLocation, timeoutTime, timeoutUnit)); } // Last Location @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) public Maybe lastLocation() { return Maybe.create(new LocationLastMaybeOnSubscribe(rxLocation)); } // Location Availability @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) public Single isLocationAvailable() { return Single.create(new LocationAvailabilitySingleOnSubscribe(rxLocation)); } // Location Updates @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) public Observable updates(@NonNull LocationRequest locationRequest) { return updatesInternal(locationRequest, null, null, null, BackpressureStrategy.MISSING).toObservable(); } @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) public Observable updates(@NonNull LocationRequest locationRequest, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return updatesInternal(locationRequest, null, timeoutTime, timeoutUnit, BackpressureStrategy.MISSING).toObservable(); } @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) public Observable updates(@NonNull LocationRequest locationRequest, @NonNull Looper looper) { return updatesInternal(locationRequest, looper, null, null, BackpressureStrategy.MISSING).toObservable(); } @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) public Observable updates(@NonNull LocationRequest locationRequest, @NonNull Looper looper, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return updatesInternal(locationRequest, looper, timeoutTime, timeoutUnit, BackpressureStrategy.MISSING).toObservable(); } @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) public Flowable updates(@NonNull LocationRequest locationRequest, BackpressureStrategy backpressureStrategy) { return updatesInternal(locationRequest, null, null, null, backpressureStrategy); } @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) public Flowable updates(@NonNull LocationRequest locationRequest, long timeoutTime, @NonNull TimeUnit timeoutUnit, BackpressureStrategy backpressureStrategy) { return updatesInternal(locationRequest, null, timeoutTime, timeoutUnit, backpressureStrategy); } @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) public Flowable updates(@NonNull LocationRequest locationRequest, @NonNull Looper looper, BackpressureStrategy backpressureStrategy) { return updatesInternal(locationRequest, looper, null, null, backpressureStrategy); } @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) public Flowable updates(@NonNull LocationRequest locationRequest, @NonNull Looper looper, long timeoutTime, @NonNull TimeUnit timeoutUnit, BackpressureStrategy backpressureStrategy) { return updatesInternal(locationRequest, looper, timeoutTime, timeoutUnit, backpressureStrategy); } private Flowable updatesInternal(LocationRequest locationRequest, Looper looper, Long timeoutTime, TimeUnit timeoutUnit, BackpressureStrategy backpressureStrategy) { return Flowable.create(new LocationUpdatesFlowableOnSubscribe(rxLocation, locationRequest, looper, timeoutTime, timeoutUnit), backpressureStrategy); } // Request Updates @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) public Single requestUpdates(@NonNull LocationRequest locationRequest, @NonNull PendingIntent pendingIntent) { return requestUpdatesInternal(locationRequest, pendingIntent, null, null); } @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) public Single requestUpdates(@NonNull LocationRequest locationRequest, @NonNull PendingIntent pendingIntent, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return requestUpdatesInternal(locationRequest, pendingIntent, timeoutTime, timeoutUnit); } private Single requestUpdatesInternal(LocationRequest locationRequest, PendingIntent pendingIntent, Long timeoutTime, TimeUnit timeoutUnit) { return Single.create(new LocationRequestUpdatesSingleOnSubscribe(rxLocation, locationRequest, pendingIntent, timeoutTime, timeoutUnit)); } // Remove Updates public Single removeUpdates(@NonNull PendingIntent pendingIntent) { return removeUpdatesInternal(pendingIntent, null, null); } public Single removeUpdates(@NonNull PendingIntent pendingIntent, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return removeUpdatesInternal(pendingIntent, timeoutTime, timeoutUnit); } private Single removeUpdatesInternal(PendingIntent pendingIntent, Long timeoutTime, TimeUnit timeoutUnit) { return Single.create(new LocationRemoveUpdatesSingleOnSubscribe(rxLocation, pendingIntent, timeoutTime, timeoutUnit)); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/Geocoding.java ================================================ package com.patloew.rxlocation; import android.content.Context; import android.location.Address; import android.location.Geocoder; import android.location.Location; import android.support.annotation.NonNull; import java.util.List; import java.util.Locale; import io.reactivex.Maybe; import io.reactivex.Single; import io.reactivex.functions.Function; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class Geocoding { private static final Function, Maybe
> ADDRESS_MAYBE_FUNCTION = addresses -> addresses.isEmpty() ? Maybe.empty(): Maybe.just(addresses.get(0)); private final Context context; Geocoding(Context context) { this.context = context; } Geocoder getGeocoder(Locale locale) { if(locale != null) { return new Geocoder(context, locale); } else { return new Geocoder(context); } } public Maybe
fromLocation(@NonNull Location location) { return fromLocation(null, location, 1).flatMapMaybe(ADDRESS_MAYBE_FUNCTION); } public Maybe
fromLocation(Locale locale, @NonNull Location location) { return fromLocation(locale, location, 1).flatMapMaybe(ADDRESS_MAYBE_FUNCTION); } public Single> fromLocation(@NonNull Location location, int maxResults) { return fromLocation(null, location, maxResults); } public Single> fromLocation(Locale locale, @NonNull Location location, int maxResults) { return Single.fromCallable(() -> getGeocoder(locale).getFromLocation(location.getLatitude(), location.getLongitude(), maxResults)); } public Maybe
fromLocation(double latitude, double longitude) { return fromLocation(null, latitude, longitude, 1).flatMapMaybe(ADDRESS_MAYBE_FUNCTION); } public Maybe
fromLocation(Locale locale, double latitude, double longitude) { return fromLocation(locale, latitude, longitude, 1).flatMapMaybe(ADDRESS_MAYBE_FUNCTION); } public Single> fromLocation(double latitude, double longitude, int maxResults) { return fromLocation(null, latitude, longitude, maxResults); } public Single> fromLocation(Locale locale, double latitude, double longitude, int maxResults) { return Single.fromCallable(() -> getGeocoder(locale).getFromLocation(latitude, longitude, maxResults)); } public Maybe
fromLocationName(@NonNull String locationName, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude) { return fromLocationName(null, locationName, 1, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude).flatMapMaybe(ADDRESS_MAYBE_FUNCTION); } public Maybe
fromLocationName(Locale locale, @NonNull String locationName, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude) { return fromLocationName(locale, locationName, 1, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude).flatMapMaybe(ADDRESS_MAYBE_FUNCTION); } public Single> fromLocationName(@NonNull String locationName, int maxResults, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude) { return fromLocationName(null, locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude); } public Single> fromLocationName(Locale locale, @NonNull String locationName, int maxResults, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude) { return Single.fromCallable(() -> getGeocoder(locale).getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude)); } public Maybe
fromLocationName(@NonNull String locationName) { return fromLocationName(null, locationName, 1).flatMapMaybe(ADDRESS_MAYBE_FUNCTION); } public Maybe
fromLocationName(Locale locale, @NonNull String locationName) { return fromLocationName(locale, locationName, 1).flatMapMaybe(ADDRESS_MAYBE_FUNCTION); } public Single> fromLocationName(@NonNull String locationName, int maxResults) { return fromLocationName(null, locationName, maxResults); } public Single> fromLocationName(Locale locale, @NonNull String locationName, int maxResults) { return Single.fromCallable(() -> getGeocoder(locale).getFromLocationName(locationName, maxResults)); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/Geofencing.java ================================================ package com.patloew.rxlocation; import android.Manifest; import android.app.PendingIntent; import android.support.annotation.NonNull; import android.support.annotation.RequiresPermission; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.GeofencingRequest; import java.util.List; import java.util.concurrent.TimeUnit; import io.reactivex.Single; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class Geofencing { private final RxLocation rxLocation; Geofencing(RxLocation rxLocation) { this.rxLocation = rxLocation; } // Add @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION) public Single add(@NonNull GeofencingRequest geofencingRequest, @NonNull PendingIntent pendingIntent) { return addInternal(geofencingRequest, pendingIntent, null, null); } @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION) public Single add(@NonNull GeofencingRequest geofencingRequest, @NonNull PendingIntent pendingIntent, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return addInternal(geofencingRequest, pendingIntent, timeoutTime, timeoutUnit); } private Single addInternal(GeofencingRequest geofencingRequest, PendingIntent pendingIntent, Long timeoutTime, TimeUnit timeoutUnit) { return Single.create(new GeofencingAddSingleOnSubscribe(rxLocation, geofencingRequest, pendingIntent, timeoutTime, timeoutUnit)); } // Remove public Single remove(@NonNull List geofenceRequestIds) { return removeInternal(geofenceRequestIds, null, null, null); } public Single remove(@NonNull List geofenceRequestIds, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return removeInternal(geofenceRequestIds, null, timeoutTime, timeoutUnit); } public Single remove(@NonNull PendingIntent pendingIntent) { return removeInternal(null, pendingIntent, null, null); } public Single remove(@NonNull PendingIntent pendingIntent, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return removeInternal(null, pendingIntent, timeoutTime, timeoutUnit); } private Single removeInternal(List geofenceRequestIds, PendingIntent pendingIntent, Long timeoutTime, TimeUnit timeoutUnit) { return Single.create(new GeofencingRemoveSingleOnSubscribe(rxLocation, geofenceRequestIds, pendingIntent, timeoutTime, timeoutUnit)); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/GeofencingAddSingleOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.GeofencingRequest; import com.google.android.gms.location.LocationServices; import java.util.concurrent.TimeUnit; import io.reactivex.SingleEmitter; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class GeofencingAddSingleOnSubscribe extends RxLocationSingleOnSubscribe { final GeofencingRequest geofencingRequest; final PendingIntent pendingIntent; GeofencingAddSingleOnSubscribe(RxLocation rxLocation, GeofencingRequest geofencingRequest, PendingIntent pendingIntent, Long timeoutTime, TimeUnit timeoutUnit) { super(rxLocation, timeoutTime, timeoutUnit); this.geofencingRequest = geofencingRequest; this.pendingIntent = pendingIntent; } @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { //noinspection MissingPermission setupLocationPendingResult( LocationServices.GeofencingApi.addGeofences(apiClient, geofencingRequest, pendingIntent), SingleResultCallBack.get(emitter) ); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/GeofencingRemoveSingleOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationServices; import java.util.List; import java.util.concurrent.TimeUnit; import io.reactivex.SingleEmitter; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class GeofencingRemoveSingleOnSubscribe extends RxLocationSingleOnSubscribe { final List geofenceRequestIds; final PendingIntent pendingIntent; GeofencingRemoveSingleOnSubscribe(RxLocation rxLocation, List geofenceRequestIds, PendingIntent pendingIntent, Long timeoutTime, TimeUnit timeoutUnit) { super(rxLocation, timeoutTime, timeoutUnit); this.geofenceRequestIds = geofenceRequestIds; this.pendingIntent = pendingIntent; } @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { ResultCallback resultCallback = SingleResultCallBack.get(emitter); if (geofenceRequestIds != null) { setupLocationPendingResult(LocationServices.GeofencingApi.removeGeofences(apiClient, geofenceRequestIds), resultCallback); } else { setupLocationPendingResult(LocationServices.GeofencingApi.removeGeofences(apiClient, pendingIntent), resultCallback); } } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/GoogleApiClientFlowable.java ================================================ package com.patloew.rxlocation; import android.content.Context; import android.support.annotation.NonNull; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Scope; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; import io.reactivex.FlowableEmitter; /* Copyright (C) 2015 Michał Charmas (http://blog.charmas.pl) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * --------------- * * FILE MODIFIED by Patrick Löwenstein, 2016 * */ public class GoogleApiClientFlowable extends RxLocationFlowableOnSubscribe { GoogleApiClientFlowable(Context ctx, Api[] apis, Scope[] scopes) { super(ctx, apis, scopes); } @SafeVarargs public static Flowable create(@NonNull Context context, @NonNull Api... apis) { return Flowable.create(new GoogleApiClientFlowable(context, apis, null), BackpressureStrategy.LATEST); } public static Flowable create(@NonNull Context context, @NonNull Api[] apis, Scope[] scopes) { return Flowable.create(new GoogleApiClientFlowable(context, apis, scopes), BackpressureStrategy.LATEST); } @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, FlowableEmitter emitter) { emitter.onNext(apiClient); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/GoogleApiConnectionException.java ================================================ package com.patloew.rxlocation; import com.google.android.gms.common.ConnectionResult; /* Copyright (C) 2015 Michał Charmas (http://blog.charmas.pl) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class GoogleApiConnectionException extends RuntimeException { private final ConnectionResult connectionResult; GoogleApiConnectionException(String detailMessage, ConnectionResult connectionResult) { super(detailMessage); this.connectionResult = connectionResult; } public ConnectionResult getConnectionResult() { return connectionResult; } public boolean wasResolutionUnsuccessful() { if (connectionResult != null) { return connectionResult.hasResolution(); } else { return false; } } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/GoogleApiConnectionSuspendedException.java ================================================ package com.patloew.rxlocation; /* Copyright (C) 2015 Michał Charmas (http://blog.charmas.pl) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class GoogleApiConnectionSuspendedException extends RuntimeException { private final int cause; GoogleApiConnectionSuspendedException(int cause) { this.cause = cause; } public int getErrorCause() { return cause; } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/LocationAvailabilitySingleOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.support.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.LocationAvailability; import com.google.android.gms.location.LocationServices; import io.reactivex.SingleEmitter; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class LocationAvailabilitySingleOnSubscribe extends RxLocationSingleOnSubscribe { LocationAvailabilitySingleOnSubscribe(@NonNull RxLocation rxLocation) { super(rxLocation, null, null); } @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { //noinspection MissingPermission LocationAvailability locationAvailability = LocationServices.FusedLocationApi.getLocationAvailability(apiClient); if (locationAvailability != null) { emitter.onSuccess(locationAvailability.isLocationAvailable()); } else { emitter.onSuccess(false); } } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/LocationFlushSingleOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.support.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationServices; import java.util.concurrent.TimeUnit; import io.reactivex.SingleEmitter; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class LocationFlushSingleOnSubscribe extends RxLocationSingleOnSubscribe { LocationFlushSingleOnSubscribe(@NonNull RxLocation rxLocation, Long timeout, TimeUnit timeUnit) { super(rxLocation, timeout, timeUnit); } @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { setupLocationPendingResult( LocationServices.FusedLocationApi.flushLocations(apiClient), SingleResultCallBack.get(emitter) ); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/LocationLastMaybeOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.location.Location; import android.support.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.LocationServices; import io.reactivex.MaybeEmitter; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class LocationLastMaybeOnSubscribe extends RxLocationMaybeOnSubscribe { LocationLastMaybeOnSubscribe(@NonNull RxLocation rxLocation) { super(rxLocation, null, null); } @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, MaybeEmitter emitter) { //noinspection MissingPermission Location location = LocationServices.FusedLocationApi.getLastLocation(apiClient); if (location != null) { emitter.onSuccess(location); } else { emitter.onComplete(); } } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/LocationRemoveUpdatesSingleOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import android.support.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationServices; import java.util.concurrent.TimeUnit; import io.reactivex.SingleEmitter; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class LocationRemoveUpdatesSingleOnSubscribe extends RxLocationSingleOnSubscribe { final PendingIntent pendingIntent; LocationRemoveUpdatesSingleOnSubscribe(@NonNull RxLocation rxLocation, PendingIntent pendingIntent, Long timeout, TimeUnit timeUnit) { super(rxLocation, timeout, timeUnit); this.pendingIntent = pendingIntent; } @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { setupLocationPendingResult( LocationServices.FusedLocationApi.removeLocationUpdates(apiClient, pendingIntent), SingleResultCallBack.get(emitter) ); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/LocationRequestUpdatesSingleOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import android.support.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import java.util.concurrent.TimeUnit; import io.reactivex.SingleEmitter; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class LocationRequestUpdatesSingleOnSubscribe extends RxLocationSingleOnSubscribe { final LocationRequest locationRequest; final PendingIntent pendingIntent; LocationRequestUpdatesSingleOnSubscribe(@NonNull RxLocation rxLocation, LocationRequest locationRequest, PendingIntent pendingIntent, Long timeout, TimeUnit timeUnit) { super(rxLocation, timeout, timeUnit); this.locationRequest = locationRequest; this.pendingIntent = pendingIntent; } @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { //noinspection MissingPermission setupLocationPendingResult( LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, locationRequest, pendingIntent), SingleResultCallBack.get(emitter) ); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/LocationSettings.java ================================================ package com.patloew.rxlocation; import android.support.annotation.NonNull; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationSettingsRequest; import com.google.android.gms.location.LocationSettingsResult; import java.util.concurrent.TimeUnit; import io.reactivex.Completable; import io.reactivex.Single; import io.reactivex.functions.Function; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class LocationSettings { private final RxLocation rxLocation; LocationSettings(RxLocation rxLocation) { this.rxLocation = rxLocation; } LocationSettingsRequest.Builder getLocationSettingsRequestBuilder() { return new LocationSettingsRequest.Builder(); } // Check public Single check(@NonNull LocationRequest locationRequest) { return checkInternal(getLocationSettingsRequestBuilder().addLocationRequest(locationRequest).build(), null, null); } public Single check(@NonNull LocationRequest locationRequest, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return checkInternal(getLocationSettingsRequestBuilder().addLocationRequest(locationRequest).build(), timeoutTime, timeoutUnit); } public Single check(@NonNull LocationSettingsRequest locationSettingsRequest) { return checkInternal(locationSettingsRequest, null, null); } public Single check(@NonNull LocationSettingsRequest locationSettingsRequest, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return checkInternal(locationSettingsRequest, timeoutTime, timeoutUnit); } private Single checkInternal(LocationSettingsRequest locationSettingsRequest, Long timeoutTime, TimeUnit timeoutUnit) { return Single.create(new SettingsCheckSingleOnSubscribe(rxLocation, locationSettingsRequest, timeoutTime, timeoutUnit)); } // Check and handle resolution private static final Function CHECK_SETTINGS_COMPLETABLE_FUNCTION = success -> success ? Completable.complete() : Completable.error(new LocationSettingsNotSatisfiedException()); public Completable checkAndHandleResolutionCompletable(@NonNull LocationRequest locationRequest) { return checkAndHandleResolution(locationRequest).flatMapCompletable(CHECK_SETTINGS_COMPLETABLE_FUNCTION); } public Single checkAndHandleResolution(@NonNull LocationRequest locationRequest) { return checkAndHandleResolutionInternal(getLocationSettingsRequestBuilder().addLocationRequest(locationRequest).build(), null, null); } public Completable checkAndHandleResolutionCompletable(@NonNull LocationRequest locationRequest, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return checkAndHandleResolution(locationRequest, timeoutTime, timeoutUnit).flatMapCompletable(CHECK_SETTINGS_COMPLETABLE_FUNCTION); } public Single checkAndHandleResolution(@NonNull LocationRequest locationRequest, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return checkAndHandleResolutionInternal(getLocationSettingsRequestBuilder().addLocationRequest(locationRequest).build(), timeoutTime, timeoutUnit); } public Completable checkAndHandleResolutionCompletable(@NonNull LocationSettingsRequest locationSettingsRequest) { return checkAndHandleResolutionInternal(locationSettingsRequest, null, null).flatMapCompletable(CHECK_SETTINGS_COMPLETABLE_FUNCTION); } public Single checkAndHandleResolution(@NonNull LocationSettingsRequest locationSettingsRequest) { return checkAndHandleResolutionInternal(locationSettingsRequest, null, null); } public Completable checkAndHandleResolutionCompletable(@NonNull LocationSettingsRequest locationSettingsRequest, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return checkAndHandleResolutionInternal(locationSettingsRequest, timeoutTime, timeoutUnit).flatMapCompletable(CHECK_SETTINGS_COMPLETABLE_FUNCTION); } public Single checkAndHandleResolution(@NonNull LocationSettingsRequest locationSettingsRequest, long timeoutTime, @NonNull TimeUnit timeoutUnit) { return checkAndHandleResolutionInternal(locationSettingsRequest, timeoutTime, timeoutUnit); } private Single checkAndHandleResolutionInternal(LocationSettingsRequest locationSettingsRequest, Long timeoutTime, TimeUnit timeoutUnit) { return Single.create(new SettingsCheckHandleSingleOnSubscribe(rxLocation, locationSettingsRequest, timeoutTime, timeoutUnit)); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/LocationSettingsActivity.java ================================================ package com.patloew.rxlocation; import android.app.Activity; import android.content.Intent; import android.content.IntentSender; import android.os.Bundle; import com.google.android.gms.common.api.Status; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class LocationSettingsActivity extends Activity { protected static final String ARG_STATUS = "status"; protected static final String ARG_ID = "id"; static final int REQUEST_CODE_RESOLUTION = 123; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState == null) { handleIntent(); } } @Override protected void onNewIntent(Intent intent) { setIntent(intent); handleIntent(); } void handleIntent() { Status status = getIntent().getParcelableExtra(ARG_STATUS); try { status.startResolutionForResult(this, REQUEST_CODE_RESOLUTION); } catch (IntentSender.SendIntentException | NullPointerException e) { setResolutionResultAndFinish(Activity.RESULT_CANCELED); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_RESOLUTION) { setResolutionResultAndFinish(resultCode); } else { setResolutionResultAndFinish(Activity.RESULT_CANCELED); } } void setResolutionResultAndFinish(int resultCode) { SettingsCheckHandleSingleOnSubscribe.onResolutionResult(getIntent().getStringExtra(ARG_ID), resultCode); finish(); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/LocationSettingsNotSatisfiedException.java ================================================ package com.patloew.rxlocation; /* Copyright (C) 2015 Michał Charmas (http://blog.charmas.pl) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class LocationSettingsNotSatisfiedException extends Exception { } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/LocationUpdatesFlowableOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.location.Location; import android.os.Looper; import android.support.annotation.NonNull; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.LocationListener; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import java.util.concurrent.TimeUnit; import io.reactivex.FlowableEmitter; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class LocationUpdatesFlowableOnSubscribe extends RxLocationFlowableOnSubscribe { final LocationRequest locationRequest; final Looper looper; RxLocationListener locationListener; protected LocationUpdatesFlowableOnSubscribe(@NonNull RxLocation rxLocation, LocationRequest locationRequest, Looper looper, Long timeout, TimeUnit timeUnit) { super(rxLocation, timeout, timeUnit); this.locationRequest = locationRequest; this.looper = looper; } @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, FlowableEmitter emitter) { locationListener = new RxLocationListener(emitter); //noinspection MissingPermission setupLocationPendingResult( LocationServices.FusedLocationApi.requestLocationUpdates(apiClient, locationRequest, locationListener, looper), new StatusErrorResultCallBack(emitter) ); } @Override protected void onUnsubscribed(GoogleApiClient apiClient) { if(locationListener != null) { LocationServices.FusedLocationApi.removeLocationUpdates(apiClient, locationListener); locationListener.onUnsubscribed(); locationListener = null; } } static class RxLocationListener implements LocationListener { private FlowableEmitter emitter; RxLocationListener(FlowableEmitter emitter) { this.emitter = emitter; } void onUnsubscribed() { emitter = null; } @Override public void onLocationChanged(Location location) { if(emitter != null) { emitter.onNext(location); } } } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/RxLocation.java ================================================ package com.patloew.rxlocation; import android.content.Context; import android.support.annotation.NonNull; import java.util.concurrent.TimeUnit; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ----------------------------- * * Make sure to have the Location permission from on Marshmallow on, * if they are needed by your requests. */ public class RxLocation { final Context ctx; private final ActivityRecognition activityRecognition = new ActivityRecognition(this); private final FusedLocation fusedLocation = new FusedLocation(this); private final Geocoding geocoding; private final Geofencing geofencing = new Geofencing(this); private final LocationSettings locationSettings = new LocationSettings(this); Long timeoutTime = null; TimeUnit timeoutUnit = null; /* Creates a new RxLocation instance. * * @param ctx Context. */ public RxLocation(@NonNull Context ctx) { this.ctx = ctx.getApplicationContext(); this.geocoding = new Geocoding(ctx.getApplicationContext()); } /* Set a default timeout for all requests to the Location APIs made in the lib. * When a timeout occurs, onError() is called with a StatusException. */ public void setDefaultTimeout(long time, @NonNull TimeUnit timeUnit) { if (timeUnit != null) { timeoutTime = time; timeoutUnit = timeUnit; } else { throw new IllegalArgumentException("timeUnit parameter must not be null"); } } /* Reset the default timeout. */ public void resetDefaultTimeout() { timeoutTime = null; timeoutUnit = null; } public ActivityRecognition activity() { return activityRecognition; } public Geocoding geocoding() { return geocoding; } public Geofencing geofencing() { return geofencing; } public FusedLocation location() { return fusedLocation; } public LocationSettings settings() { return locationSettings; } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/RxLocationBaseOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.content.Context; import android.support.annotation.NonNull; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.Result; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Scope; import com.google.android.gms.location.ActivityRecognition; import com.google.android.gms.location.LocationServices; import java.util.concurrent.TimeUnit; /* Copyright (C) 2015 Michał Charmas (http://blog.charmas.pl) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * --------------- * * FILE MODIFIED by Patrick Löwenstein, 2016 * */ abstract class RxLocationBaseOnSubscribe { protected final Context ctx; final Long timeoutTime; final TimeUnit timeoutUnit; private final Api[] services; private final Scope[] scopes; protected RxLocationBaseOnSubscribe(@NonNull RxLocation rxLocation, Long timeout, TimeUnit timeUnit) { this.ctx = rxLocation.ctx; this.services = new Api[]{ LocationServices.API, ActivityRecognition.API }; this.scopes = null; if (timeout != null && timeUnit != null) { this.timeoutTime = timeout; this.timeoutUnit = timeUnit; } else { this.timeoutTime = rxLocation.timeoutTime; this.timeoutUnit = rxLocation.timeoutUnit; } } protected RxLocationBaseOnSubscribe(@NonNull Context ctx, @NonNull Api[] services, Scope[] scopes) { this.ctx = ctx; this.services = services; this.scopes = scopes; timeoutTime = null; timeoutUnit = null; } protected final void setupLocationPendingResult(PendingResult pendingResult, ResultCallback resultCallback) { if (timeoutTime != null && timeoutUnit != null) { pendingResult.setResultCallback(resultCallback, timeoutTime, timeoutUnit); } else { pendingResult.setResultCallback(resultCallback); } } protected GoogleApiClient.Builder getApiClientBuilder() { return new GoogleApiClient.Builder(ctx); } protected GoogleApiClient createApiClient(ApiClientConnectionCallbacks apiClientConnectionCallbacks) { GoogleApiClient.Builder apiClientBuilder = getApiClientBuilder(); for (Api service : services) { apiClientBuilder.addApi(service); } if (scopes != null) { for (Scope scope : scopes) { apiClientBuilder.addScope(scope); } } apiClientBuilder.addConnectionCallbacks(apiClientConnectionCallbacks); apiClientBuilder.addOnConnectionFailedListener(apiClientConnectionCallbacks); GoogleApiClient apiClient = apiClientBuilder.build(); apiClientConnectionCallbacks.setClient(apiClient); return apiClient; } protected void onUnsubscribed(GoogleApiClient apiClient) { } protected abstract class ApiClientConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { protected GoogleApiClient apiClient; protected ApiClientConnectionCallbacks() { } public void setClient(GoogleApiClient client) { this.apiClient = client; } } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/RxLocationFlowableOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.content.Context; import android.os.Bundle; import android.support.annotation.NonNull; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Scope; import java.util.concurrent.TimeUnit; import io.reactivex.FlowableEmitter; import io.reactivex.FlowableOnSubscribe; /* Copyright (C) 2015 Michał Charmas (http://blog.charmas.pl) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * --------------- * * FILE MODIFIED by Patrick Löwenstein, 2016 * */ abstract class RxLocationFlowableOnSubscribe extends RxLocationBaseOnSubscribe implements FlowableOnSubscribe { protected RxLocationFlowableOnSubscribe(@NonNull RxLocation rxLocation, Long timeout, TimeUnit timeUnit) { super(rxLocation, timeout, timeUnit); } protected RxLocationFlowableOnSubscribe(@NonNull Context ctx, @NonNull Api[] services, Scope[] scopes) { super(ctx, services, scopes); } @Override public final void subscribe(FlowableEmitter emitter) throws Exception { final GoogleApiClient apiClient = createApiClient(new ApiClientConnectionCallbacks(emitter)); try { apiClient.connect(); } catch (Throwable ex) { emitter.onError(ex); } emitter.setCancellable(() -> { if (apiClient.isConnected()) { onUnsubscribed(apiClient); } apiClient.disconnect(); }); } protected abstract void onGoogleApiClientReady(GoogleApiClient apiClient, FlowableEmitter emitter); protected class ApiClientConnectionCallbacks extends RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks { final protected FlowableEmitter emitter; private GoogleApiClient apiClient; private ApiClientConnectionCallbacks(FlowableEmitter emitter) { this.emitter = emitter; } @Override public void onConnected(Bundle bundle) { try { onGoogleApiClientReady(apiClient, emitter); } catch (Throwable ex) { emitter.onError(ex); } } @Override public void onConnectionSuspended(int cause) { emitter.onError(new GoogleApiConnectionSuspendedException(cause)); } @Override public void onConnectionFailed(ConnectionResult connectionResult) { emitter.onError(new GoogleApiConnectionException("Error connecting to GoogleApiClient.", connectionResult)); } public void setClient(GoogleApiClient client) { this.apiClient = client; } } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/RxLocationMaybeOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.content.Context; import android.os.Bundle; import android.support.annotation.NonNull; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Scope; import java.util.concurrent.TimeUnit; import io.reactivex.MaybeEmitter; import io.reactivex.MaybeOnSubscribe; /* Copyright (C) 2015 Michał Charmas (http://blog.charmas.pl) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * --------------- * * FILE MODIFIED by Patrick Löwenstein, 2016 * */ abstract class RxLocationMaybeOnSubscribe extends RxLocationBaseOnSubscribe implements MaybeOnSubscribe { protected RxLocationMaybeOnSubscribe(@NonNull RxLocation rxLocation, Long timeout, TimeUnit timeUnit) { super(rxLocation, timeout, timeUnit); } protected RxLocationMaybeOnSubscribe(@NonNull Context ctx, @NonNull Api[] services, Scope[] scopes) { super(ctx, services, scopes); } @Override public final void subscribe(MaybeEmitter emitter) throws Exception { final GoogleApiClient apiClient = createApiClient(new ApiClientConnectionCallbacks(emitter)); try { apiClient.connect(); } catch (Throwable ex) { emitter.onError(ex); } emitter.setCancellable(() -> { if (apiClient.isConnected()) { onUnsubscribed(apiClient); } apiClient.disconnect(); }); } protected abstract void onGoogleApiClientReady(GoogleApiClient apiClient, MaybeEmitter emitter); protected class ApiClientConnectionCallbacks extends RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks { final protected MaybeEmitter emitter; private GoogleApiClient apiClient; private ApiClientConnectionCallbacks(MaybeEmitter emitter) { this.emitter = emitter; } @Override public void onConnected(Bundle bundle) { try { onGoogleApiClientReady(apiClient, emitter); } catch (Throwable ex) { emitter.onError(ex); } } @Override public void onConnectionSuspended(int cause) { emitter.onError(new GoogleApiConnectionSuspendedException(cause)); } @Override public void onConnectionFailed(ConnectionResult connectionResult) { emitter.onError(new GoogleApiConnectionException("Error connecting to GoogleApiClient.", connectionResult)); } public void setClient(GoogleApiClient client) { this.apiClient = client; } } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/RxLocationSingleOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.content.Context; import android.os.Bundle; import android.support.annotation.NonNull; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Scope; import java.util.concurrent.TimeUnit; import io.reactivex.SingleEmitter; import io.reactivex.SingleOnSubscribe; /* Copyright (C) 2015 Michał Charmas (http://blog.charmas.pl) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * --------------- * * FILE MODIFIED by Patrick Löwenstein, 2016 * */ abstract class RxLocationSingleOnSubscribe extends RxLocationBaseOnSubscribe implements SingleOnSubscribe { protected RxLocationSingleOnSubscribe(@NonNull RxLocation rxLocation, Long timeout, TimeUnit timeUnit) { super(rxLocation, timeout, timeUnit); } protected RxLocationSingleOnSubscribe(@NonNull Context ctx, @NonNull Api[] services, Scope[] scopes) { super(ctx, services, scopes); } @Override public final void subscribe(SingleEmitter emitter) throws Exception { final GoogleApiClient apiClient = createApiClient(new ApiClientConnectionCallbacks(emitter)); try { apiClient.connect(); } catch (Throwable ex) { emitter.onError(ex); } emitter.setCancellable(() -> { if (apiClient.isConnected()) { onUnsubscribed(apiClient); } apiClient.disconnect(); }); } protected abstract void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter); protected class ApiClientConnectionCallbacks extends RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks { final protected SingleEmitter emitter; private GoogleApiClient apiClient; private ApiClientConnectionCallbacks(SingleEmitter emitter) { this.emitter = emitter; } @Override public void onConnected(Bundle bundle) { try { onGoogleApiClientReady(apiClient, emitter); } catch (Throwable ex) { emitter.onError(ex); } } @Override public void onConnectionSuspended(int cause) { emitter.onError(new GoogleApiConnectionSuspendedException(cause)); } @Override public void onConnectionFailed(ConnectionResult connectionResult) { emitter.onError(new GoogleApiConnectionException("Error connecting to GoogleApiClient.", connectionResult)); } public void setClient(GoogleApiClient client) { this.apiClient = client; } } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/SettingsCheckHandleSingleOnSubscribe.java ================================================ package com.patloew.rxlocation; import android.app.Activity; import android.content.Context; import android.content.Intent; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.LocationSettingsRequest; import com.google.android.gms.location.LocationSettingsStatusCodes; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.UUID; import java.util.concurrent.TimeUnit; import io.reactivex.SingleEmitter; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class SettingsCheckHandleSingleOnSubscribe extends RxLocationSingleOnSubscribe { static final Map> observableMap = new HashMap<>(); final Context context; final LocationSettingsRequest locationSettingsRequest; private WeakReference> emitterWeakRef; SettingsCheckHandleSingleOnSubscribe(RxLocation rxLocation, LocationSettingsRequest locationSettingsRequest, Long timeoutTime, TimeUnit timeoutUnit) { super(rxLocation, timeoutTime, timeoutUnit); this.context = rxLocation.ctx; this.locationSettingsRequest = locationSettingsRequest; } static void onResolutionResult(String observableId, int resultCode) { if (observableMap.containsKey(observableId)) { SettingsCheckHandleSingleOnSubscribe observable = observableMap.get(observableId).get(); if (observable != null && observable.emitterWeakRef != null) { SingleEmitter observer = observable.emitterWeakRef.get(); if (observer != null) { observer.onSuccess(resultCode == Activity.RESULT_OK); } } observableMap.remove(observableId); } observableMapCleanup(); } static void observableMapCleanup() { if(!observableMap.isEmpty()) { Iterator>> it = observableMap.entrySet().iterator(); while(it.hasNext()) { Map.Entry> entry = it.next(); if(entry.getValue().get() == null) { it.remove(); } } } } @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { emitterWeakRef = new WeakReference<>(emitter); setupLocationPendingResult( LocationServices.SettingsApi.checkLocationSettings(apiClient, locationSettingsRequest), result -> { Status status = result.getStatus(); switch (status.getStatusCode()) { case LocationSettingsStatusCodes.SUCCESS: // All location settings are satisfied. The client can initialize location // requests here. emitter.onSuccess(true); break; case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: // Location settings are not satisfied. But could be fixed by showing the user // a dialog. if (context != null) { String observableId = UUID.randomUUID().toString(); observableMap.put(observableId, new WeakReference<>(SettingsCheckHandleSingleOnSubscribe.this)); Intent intent = new Intent(context, LocationSettingsActivity.class); intent.putExtra(LocationSettingsActivity.ARG_STATUS, status); intent.putExtra(LocationSettingsActivity.ARG_ID, observableId); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } else { emitter.onSuccess(false); } break; case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: // Location settings are not satisfied. However, we have no way to fix the // settings so we won't show the dialog. emitter.onSuccess(false); break; default: emitter.onError(new StatusException(result)); break; } } ); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/SettingsCheckSingleOnSubscribe.java ================================================ package com.patloew.rxlocation; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.LocationSettingsRequest; import com.google.android.gms.location.LocationSettingsResult; import java.util.concurrent.TimeUnit; import io.reactivex.SingleEmitter; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class SettingsCheckSingleOnSubscribe extends RxLocationSingleOnSubscribe { final LocationSettingsRequest locationSettingsRequest; SettingsCheckSingleOnSubscribe(RxLocation rxLocation, LocationSettingsRequest locationSettingsRequest, Long timeoutTime, TimeUnit timeoutUnit) { super(rxLocation, timeoutTime, timeoutUnit); this.locationSettingsRequest = locationSettingsRequest; } @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { setupLocationPendingResult( LocationServices.SettingsApi.checkLocationSettings(apiClient, locationSettingsRequest), SingleResultCallBack.get(emitter) ); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/SingleResultCallBack.java ================================================ package com.patloew.rxlocation; import android.support.annotation.NonNull; import com.google.android.gms.common.api.Result; import com.google.android.gms.common.api.ResultCallback; import io.reactivex.SingleEmitter; import io.reactivex.functions.Function; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class SingleResultCallBack implements ResultCallback { private static final Function ID_FUNC = input -> input; private final SingleEmitter emitter; private final Function mapper; private SingleResultCallBack(@NonNull SingleEmitter emitter, @NonNull Function mapper) { this.emitter = emitter; this.mapper = mapper; } static ResultCallback get(@NonNull SingleEmitter emitter, @NonNull Function mapper) { return new SingleResultCallBack<>(emitter, mapper); } static ResultCallback get(@NonNull SingleEmitter emitter) { //noinspection unchecked return new SingleResultCallBack<>(emitter, ID_FUNC); } @Override public void onResult(@NonNull T result) { if (!result.getStatus().isSuccess()) { emitter.onError(new StatusException(result)); } else { try { emitter.onSuccess(mapper.apply(result)); } catch (Exception e) { emitter.onError(e); } } } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/StatusErrorResultCallBack.java ================================================ package com.patloew.rxlocation; import android.support.annotation.NonNull; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Status; import io.reactivex.FlowableEmitter; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class StatusErrorResultCallBack implements ResultCallback { private final FlowableEmitter emitter; StatusErrorResultCallBack(@NonNull FlowableEmitter emitter) { this.emitter = emitter; } @Override public void onResult(@NonNull Status status) { if (!status.isSuccess()) { emitter.onError(new StatusException(status)); } } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/StatusException.java ================================================ package com.patloew.rxlocation; import com.google.android.gms.common.api.Result; import com.google.android.gms.common.api.Status; /* Copyright (C) 2015 Michał Charmas (http://blog.charmas.pl) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * ------------ * * * FILE MODIFIED by Patrick Löwenstein 2017 */ public class StatusException extends RuntimeException { private final Result result; public StatusException(Result result) { super(result.getStatus().toString()); this.result = result; } public Result getResult() { return result; } public Status getStatus() { return result.getStatus(); } } ================================================ FILE: library/src/main/java/com/patloew/rxlocation/StatusExceptionResumeNextTransformer.java ================================================ package com.patloew.rxlocation; import com.google.android.gms.common.api.Result; import io.reactivex.Flowable; import io.reactivex.FlowableTransformer; import io.reactivex.Observable; import io.reactivex.ObservableTransformer; import io.reactivex.Single; import io.reactivex.SingleTransformer; /* Copyright 2017 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class StatusExceptionResumeNextTransformer { public static FlowableTransformer forFlowable() { return upstream -> upstream.onErrorResumeNext(throwable -> { if(throwable instanceof StatusException) { StatusException statusException = (StatusException) throwable; if(statusException.getStatus().hasResolution()) { return Flowable.just((R) statusException.getResult()); } else { return Flowable.error(throwable); } } else { return Flowable.error(throwable); } }); } public static ObservableTransformer forObservable() { return upstream -> upstream.onErrorResumeNext(throwable -> { if(throwable instanceof StatusException) { StatusException statusException = (StatusException) throwable; if(statusException.getStatus().hasResolution()) { return Observable.just((R) statusException.getResult()); } else { return Observable.error(throwable); } } else { return Observable.error(throwable); } }); } public static SingleTransformer forSingle() { return upstream -> upstream.onErrorResumeNext(throwable -> { if(throwable instanceof StatusException) { StatusException statusException = (StatusException) throwable; if(statusException.getStatus().hasResolution()) { return Single.just((R) statusException.getResult()); } else { return Single.error(throwable); } } else { return Single.error(throwable); } }); } } ================================================ FILE: library/src/main/res/values/strings.xml ================================================ Patrick Löwenstein https://nullpointer.wtf RxLocation This library wraps the Location APIs in RxJava 2 Observables, Singles and Maybes. No more managing GoogleApiClients! Also, the resolution of the location settings check is optionally handled by the lib. https://github.com/patloew/RxLocation 1.0.2 true https://github.com/patloew/RxLocation com.patloew.rxlocation.RxLocation apache_2_0 ================================================ FILE: library/src/test/java/com/patloew/rxlocation/ActivityOnSubscribeTest.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import android.content.Context; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationServices; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import io.reactivex.Single; import static org.powermock.api.mockito.PowerMockito.doReturn; import static org.powermock.api.mockito.PowerMockito.when; @SuppressWarnings("MissingPermission") @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ LocationServices.class, com.google.android.gms.location.ActivityRecognition.class, Status.class, ConnectionResult.class, RxLocationBaseOnSubscribe.class }) public class ActivityOnSubscribeTest extends BaseOnSubscribeTest { @Mock PendingIntent pendingIntent; @Override @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); super.setup(); } // ActivityRequestUpdatesSingle @Test public void ActivityRequestUpdatesSingle_Success() { ActivityRequestUpdatesSingleOnSubscribe single = PowerMockito.spy(new ActivityRequestUpdatesSingleOnSubscribe(rxLocation, 1L, pendingIntent, null, null)); setPendingResultValue(status); doReturn(true).when(status).isSuccess(); doReturn(pendingResult).when(activityRecognitionApi).requestActivityUpdates(apiClient, 1L, pendingIntent); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), status); } @Test public void ActivityRequestUpdatesSingle_StatusException() { ActivityRequestUpdatesSingleOnSubscribe single = PowerMockito.spy(new ActivityRequestUpdatesSingleOnSubscribe(rxLocation, 1L, pendingIntent, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(false); when(activityRecognitionApi.requestActivityUpdates(apiClient, 1L, pendingIntent)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertError(Single.create(single).test(), StatusException.class); } // ActivityRemoveUpdatesSingle @Test public void ActivityRemoveUpdatesSingle_Success() { ActivityRemoveUpdatesSingleOnSubscribe single = PowerMockito.spy(new ActivityRemoveUpdatesSingleOnSubscribe(rxLocation, pendingIntent, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(true); when(activityRecognitionApi.removeActivityUpdates(apiClient, pendingIntent)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), status); } @Test public void ActivityRemoveUpdatesSingle_StatusException() { ActivityRemoveUpdatesSingleOnSubscribe single = PowerMockito.spy(new ActivityRemoveUpdatesSingleOnSubscribe(rxLocation, pendingIntent, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(false); when(activityRecognitionApi.removeActivityUpdates(apiClient, pendingIntent)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertError(Single.create(single).test(), StatusException.class); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/ActivityTest.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationServices; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import io.reactivex.Single; import static junit.framework.Assert.assertEquals; import static org.mockito.Mockito.times; @SuppressWarnings("MissingPermission") @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ Single.class, LocationServices.class, com.google.android.gms.location.ActivityRecognition.class, Status.class, ConnectionResult.class }) public class ActivityTest extends BaseTest { @Mock PendingIntent pendingIntent; @Override @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); PowerMockito.spy(Single.class); super.setup(); } // Request Updates @Test public void Activity_RequestUpdates() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(ActivityRequestUpdatesSingleOnSubscribe.class); long detectionIntervalMillis = 123L; rxLocation.activity().requestUpdates(detectionIntervalMillis,pendingIntent); rxLocation.activity().requestUpdates(detectionIntervalMillis,pendingIntent, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); ActivityRequestUpdatesSingleOnSubscribe single = captor.getAllValues().get(0); assertEquals(detectionIntervalMillis, single.detectionIntervalMillis); assertEquals(pendingIntent, single.pendingIntent); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(detectionIntervalMillis, single.detectionIntervalMillis); assertEquals(pendingIntent, single.pendingIntent); assertTimeoutSet(single); } // Remove Updates @Test public void Activity_RemoveUpdates() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(ActivityRemoveUpdatesSingleOnSubscribe.class); rxLocation.activity().removeUpdates(pendingIntent); rxLocation.activity().removeUpdates(pendingIntent, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); ActivityRemoveUpdatesSingleOnSubscribe single = captor.getAllValues().get(0); assertEquals(pendingIntent, single.pendingIntent); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(pendingIntent, single.pendingIntent); assertTimeoutSet(single); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/BaseOnSubscribeTest.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import android.support.annotation.CallSuper; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.Result; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.ActivityRecognition; import com.google.android.gms.location.ActivityRecognitionApi; import com.google.android.gms.location.FusedLocationProviderApi; import com.google.android.gms.location.GeofencingApi; import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.SettingsApi; import org.mockito.Matchers; import org.mockito.Mock; import org.powermock.api.mockito.PowerMockito; import org.powermock.reflect.Whitebox; import io.reactivex.FlowableEmitter; import io.reactivex.MaybeEmitter; import io.reactivex.SingleEmitter; import io.reactivex.observers.TestObserver; import io.reactivex.subscribers.TestSubscriber; import static org.powermock.api.mockito.PowerMockito.doAnswer; import static org.powermock.api.mockito.PowerMockito.doReturn; public abstract class BaseOnSubscribeTest extends BaseTest { @Mock GoogleApiClient apiClient; @Mock Status status; @Mock ConnectionResult connectionResult; @Mock PendingResult pendingResult; @Mock PendingIntent pendingIntent; @Mock FusedLocationProviderApi fusedLocationProviderApi; @Mock ActivityRecognitionApi activityRecognitionApi; @Mock GeofencingApi geofencingApi; @Mock SettingsApi settingsApi; @CallSuper public void setup() throws Exception { PowerMockito.mockStatic(LocationServices.class); PowerMockito.mockStatic(ActivityRecognition.class); Whitebox.setInternalState(LocationServices.class, fusedLocationProviderApi); Whitebox.setInternalState(LocationServices.class, geofencingApi); Whitebox.setInternalState(LocationServices.class, settingsApi); Whitebox.setInternalState(ActivityRecognition.class, activityRecognitionApi); doReturn(status).when(status).getStatus(); super.setup(); } // Mock GoogleApiClient connection success behaviour protected void setupBaseFlowableSuccess(final RxLocationFlowableOnSubscribe rxLocationFlowableOnSubscribe) { setupBaseFlowableSuccess(rxLocationFlowableOnSubscribe, apiClient); } // Mock GoogleApiClient connection success behaviour protected void setupBaseFlowableSuccess(final RxLocationFlowableOnSubscribe rxLocationFlowableOnSubscribe, final GoogleApiClient apiClient) { doAnswer(invocation -> { final FlowableEmitter subscriber = ((RxLocationFlowableOnSubscribe.ApiClientConnectionCallbacks)invocation.getArguments()[0]).emitter; doAnswer(invocation1 -> { rxLocationFlowableOnSubscribe.onGoogleApiClientReady(apiClient, subscriber); return null; }).when(apiClient).connect(); return apiClient; }).when(rxLocationFlowableOnSubscribe).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); } // Mock GoogleApiClient connection success behaviour protected void setupBaseSingleSuccess(final RxLocationSingleOnSubscribe rxLocationSingleOnSubscribe) { setupBaseSingleSuccess(rxLocationSingleOnSubscribe, apiClient); } // Mock GoogleApiClient connection success behaviour protected void setupBaseSingleSuccess(final RxLocationSingleOnSubscribe rxLocationSingleOnSubscribe, final GoogleApiClient apiClient) { doAnswer(invocation -> { final SingleEmitter subscriber = ((RxLocationSingleOnSubscribe.ApiClientConnectionCallbacks)invocation.getArguments()[0]).emitter; doAnswer(invocation1 -> { rxLocationSingleOnSubscribe.onGoogleApiClientReady(apiClient, subscriber); return null; }).when(apiClient).connect(); return apiClient; }).when(rxLocationSingleOnSubscribe).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); } // Mock GoogleApiClient connection success behaviour protected void setupBaseMaybeSuccess(final RxLocationMaybeOnSubscribe baseSingle) { setupBaseMaybeSuccess(baseSingle, apiClient); } // Mock GoogleApiClient connection success behaviour protected void setupBaseMaybeSuccess(final RxLocationMaybeOnSubscribe baseSingle, final GoogleApiClient apiClient) { doAnswer(invocation -> { final MaybeEmitter subscriber = ((RxLocationMaybeOnSubscribe.ApiClientConnectionCallbacks)invocation.getArguments()[0]).emitter; doAnswer(invocation1 -> { baseSingle.onGoogleApiClientReady(apiClient, subscriber); return null; }).when(apiClient).connect(); return apiClient; }).when(baseSingle).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); } // Mock GoogleApiClient connection error behaviour protected void setupBaseSingleError(final RxLocationSingleOnSubscribe rxLocationSingleOnSubscribe) { doAnswer(invocation -> { final SingleEmitter subscriber = ((RxLocationSingleOnSubscribe.ApiClientConnectionCallbacks)invocation.getArguments()[0]).emitter; doAnswer(invocation1 -> { subscriber.onError(new GoogleApiConnectionException("Error connecting to GoogleApiClient.", connectionResult)); return null; }).when(apiClient).connect(); return apiClient; }).when(rxLocationSingleOnSubscribe).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); } // Mock GoogleApiClient connection error behaviour protected void setupBaseFlowableError(final RxLocationFlowableOnSubscribe rxLocationFlowableOnSubscribe) { doAnswer(invocation -> { final FlowableEmitter subscriber = ((RxLocationFlowableOnSubscribe.ApiClientConnectionCallbacks)invocation.getArguments()[0]).emitter; doAnswer(invocation1 -> { subscriber.onError(new GoogleApiConnectionException("Error connecting to GoogleApiClient.", connectionResult)); return null; }).when(apiClient).connect(); return apiClient; }).when(rxLocationFlowableOnSubscribe).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); } @SuppressWarnings("unchecked") protected void setPendingResultValue(final Result result) { doAnswer(invocation -> { ((ResultCallback)invocation.getArguments()[0]).onResult(result); return null; }).when(pendingResult).setResultCallback(Matchers.any()); } protected static void assertError(TestObserver sub, Class errorClass) { sub.assertError(errorClass); sub.assertNoValues(); } protected static void assertError(TestSubscriber sub, Class errorClass) { sub.assertError(errorClass); sub.assertNoValues(); } @SuppressWarnings("unchecked") protected static void assertSingleValue(TestObserver sub, Object value) { sub.assertComplete(); sub.assertValue(value); } @SuppressWarnings("unchecked") protected static void assertSingleValue(TestSubscriber sub, Object value) { sub.assertComplete(); sub.assertValue(value); } protected static void assertNoValue(TestObserver sub) { sub.assertComplete(); sub.assertNoValues(); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/BaseTest.java ================================================ package com.patloew.rxlocation; import android.content.Context; import android.support.annotation.CallSuper; import org.mockito.Mock; import java.util.concurrent.TimeUnit; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; import static org.powermock.api.mockito.PowerMockito.doReturn; import static org.powermock.api.mockito.PowerMockito.mock; public abstract class BaseTest { protected static final long TIMEOUT_TIME = 1L; protected static final TimeUnit TIMEOUT_TIMEUNIT = TimeUnit.SECONDS; @Mock Context ctx; RxLocation rxLocation; @CallSuper public void setup() throws Exception { doReturn(ctx).when(ctx).getApplicationContext(); rxLocation = new RxLocation(ctx); } protected static final void assertNoTimeoutSet(RxLocationBaseOnSubscribe rxLocationBaseOnSubscribe) { assertNull(rxLocationBaseOnSubscribe.timeoutTime); assertNull(rxLocationBaseOnSubscribe.timeoutUnit); } protected static final void assertTimeoutSet(RxLocationBaseOnSubscribe rxLocationBaseOnSubscribe) { assertEquals(TIMEOUT_TIME, (long) rxLocationBaseOnSubscribe.timeoutTime); assertEquals(TIMEOUT_TIMEUNIT, rxLocationBaseOnSubscribe.timeoutUnit); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/GeocodingTest.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import android.location.Address; import android.location.Geocoder; import android.location.Location; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationServices; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.util.ArrayList; import java.util.List; import java.util.Locale; import io.reactivex.Single; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.powermock.api.mockito.PowerMockito.spy; @SuppressWarnings("MissingPermission") @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ Single.class, LocationServices.class, com.google.android.gms.location.ActivityRecognition.class, Status.class, ConnectionResult.class }) public class GeocodingTest extends BaseTest { @Mock PendingIntent pendingIntent; @Mock Geocoder geocoder; @Mock Address address; @Mock Location location; Locale locale = new Locale("en"); final double latitude = 1.0; final double longitude = 2.0; final String locationName = "name"; final double lowerLeftLatitude = 1.0; final double lowerLeftLongitude = 2.0; final double upperRightLatitude = 3.0; final double upperRightLongitude = 4.0; Geocoding geocoding; List
addressList; @Override @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); spy(Single.class); super.setup(); geocoding = spy(rxLocation.geocoding()); doReturn(geocoder).when(geocoding).getGeocoder(any()); doReturn(latitude).when(location).getLatitude(); doReturn(longitude).when(location).getLongitude(); addressList = new ArrayList<>(1); addressList.add(address); } private void verifyGeocoderWithLocale() { verify(geocoding).getGeocoder(locale); verify(geocoding, never()).getGeocoder(null); } private void verifyGeocoderWithNull() { verify(geocoding, never()).getGeocoder(locale); verify(geocoding).getGeocoder(null); } // fromLocation with Location @Test public void FromLocation_Location_Maybe_NoLocale() throws Exception { doReturn(addressList).when(geocoder).getFromLocation(latitude, longitude, 1); geocoding.fromLocation(location).test() .assertValue(address) .assertComplete(); verifyGeocoderWithNull(); } @Test public void FromLocation_Location_Maybe_Locale() throws Exception { doReturn(addressList).when(geocoder).getFromLocation(latitude, longitude, 1); geocoding.fromLocation(locale, location).test() .assertValue(address) .assertComplete(); verifyGeocoderWithLocale(); } @Test public void FromLocation_Location_Single_NoLocale() throws Exception { doReturn(addressList).when(geocoder).getFromLocation(latitude, longitude, 1); geocoding.fromLocation(location, 1).test() .assertValue(addressList) .assertComplete(); verifyGeocoderWithNull(); } @Test public void FromLocation_Location_Single_Locale() throws Exception { doReturn(addressList).when(geocoder).getFromLocation(latitude, longitude, 1); geocoding.fromLocation(locale, location, 1).test() .assertValue(addressList) .assertComplete(); verifyGeocoderWithLocale(); } // fromLocation with LatLong @Test public void FromLocation_LatLong_Maybe_NoLocale() throws Exception { doReturn(addressList).when(geocoder).getFromLocation(latitude, longitude, 1); geocoding.fromLocation(latitude, longitude).test() .assertValue(address) .assertComplete(); verifyGeocoderWithNull(); } @Test public void FromLocation_LatLong_Maybe_Locale() throws Exception { doReturn(addressList).when(geocoder).getFromLocation(latitude, longitude, 1); geocoding.fromLocation(locale, latitude, longitude).test() .assertValue(address) .assertComplete(); verifyGeocoderWithLocale(); } @Test public void FromLocation_LatLong_Single_NoLocale() throws Exception { doReturn(addressList).when(geocoder).getFromLocation(latitude, longitude, 1); geocoding.fromLocation(latitude, longitude, 1).test() .assertValue(addressList) .assertComplete(); verifyGeocoderWithNull(); } @Test public void FromLocation_LatLong_Single_Locale() throws Exception { doReturn(addressList).when(geocoder).getFromLocation(latitude, longitude, 1); geocoding.fromLocation(locale, latitude, longitude, 1).test() .assertValue(addressList) .assertComplete(); verifyGeocoderWithLocale(); } // fromLocationName with LatLong Rect @Test public void FromLocationName_LatLong_Maybe_NoLocale() throws Exception { doReturn(addressList).when(geocoder).getFromLocationName(locationName, 1, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude); geocoding.fromLocationName(locationName, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude).test() .assertValue(address) .assertComplete(); verifyGeocoderWithNull(); } @Test public void FromLocationName_LatLong_Maybe_Locale() throws Exception { doReturn(addressList).when(geocoder).getFromLocationName(locationName, 1, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude); geocoding.fromLocationName(locale, locationName, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude).test() .assertValue(address) .assertComplete(); verifyGeocoderWithLocale(); } @Test public void FromLocationName_LatLong_Single_NoLocale() throws Exception { doReturn(addressList).when(geocoder).getFromLocationName(locationName, 1, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude); geocoding.fromLocationName(locationName, 1, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude).test() .assertValue(addressList) .assertComplete(); verifyGeocoderWithNull(); } @Test public void FromLocationName_LatLong_Single_Locale() throws Exception { doReturn(addressList).when(geocoder).getFromLocationName(locationName, 1, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude); geocoding.fromLocationName(locale, locationName, 1, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude).test() .assertValue(addressList) .assertComplete(); verifyGeocoderWithLocale(); } // fromLocationName @Test public void FromLocationName_Maybe_NoLocale() throws Exception { doReturn(addressList).when(geocoder).getFromLocationName(locationName, 1); geocoding.fromLocationName(locationName).test() .assertValue(address) .assertComplete(); verifyGeocoderWithNull(); } @Test public void FromLocationName_Maybe_Locale() throws Exception { doReturn(addressList).when(geocoder).getFromLocationName(locationName, 1); geocoding.fromLocationName(locale, locationName).test() .assertValue(address) .assertComplete(); verifyGeocoderWithLocale(); } @Test public void FromLocationName_Single_NoLocale() throws Exception { doReturn(addressList).when(geocoder).getFromLocationName(locationName, 1); geocoding.fromLocationName(locationName, 1).test() .assertValue(addressList) .assertComplete(); verifyGeocoderWithNull(); } @Test public void FromLocationName_Single_Locale() throws Exception { doReturn(addressList).when(geocoder).getFromLocationName(locationName, 1); geocoding.fromLocationName(locale, locationName, 1).test() .assertValue(addressList) .assertComplete(); verifyGeocoderWithLocale(); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/GeofencingOnSubscribeTest.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.GeofencingRequest; import com.google.android.gms.location.LocationServices; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.util.ArrayList; import java.util.List; import io.reactivex.Single; import static org.mockito.Mockito.when; @SuppressWarnings("MissingPermission") @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ LocationServices.class, com.google.android.gms.location.ActivityRecognition.class, Status.class, ConnectionResult.class, RxLocationBaseOnSubscribe.class }) public class GeofencingOnSubscribeTest extends BaseOnSubscribeTest { @Mock GeofencingRequest geofencingRequest; @Mock PendingIntent pendingIntent; List geofenceRequestIds; @Override @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); super.setup(); geofenceRequestIds = new ArrayList<>(0); } // GeofencingAddSingle @Test public void GeofencingAddSingle_Success() { GeofencingAddSingleOnSubscribe single = PowerMockito.spy(new GeofencingAddSingleOnSubscribe(rxLocation, geofencingRequest, pendingIntent, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(true); when(geofencingApi.addGeofences(apiClient, geofencingRequest, pendingIntent)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), status); } @Test public void GeofencingAddSingle_StatusException() { GeofencingAddSingleOnSubscribe single = PowerMockito.spy(new GeofencingAddSingleOnSubscribe(rxLocation, geofencingRequest, pendingIntent, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(false); when(geofencingApi.addGeofences(apiClient, geofencingRequest, pendingIntent)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertError(Single.create(single).test(), StatusException.class); } // GeofencingRemoveSingle @Test public void GeofencingRemoveSingle_PendingIntent_Success() { GeofencingRemoveSingleOnSubscribe single = PowerMockito.spy(new GeofencingRemoveSingleOnSubscribe(rxLocation, null, pendingIntent, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(true); when(geofencingApi.removeGeofences(apiClient, pendingIntent)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), status); } @Test public void GeofencingRemoveSingle_PendingIntent_StatusException() { GeofencingRemoveSingleOnSubscribe single = PowerMockito.spy(new GeofencingRemoveSingleOnSubscribe(rxLocation, null, pendingIntent, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(false); when(geofencingApi.removeGeofences(apiClient, pendingIntent)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertError(Single.create(single).test(), StatusException.class); } @Test public void GeofencingRemoveSingle_IdList_Success() { GeofencingRemoveSingleOnSubscribe single = PowerMockito.spy(new GeofencingRemoveSingleOnSubscribe(rxLocation, geofenceRequestIds, null, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(true); when(geofencingApi.removeGeofences(apiClient, geofenceRequestIds)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), status); } @Test public void GeofencingRemoveSingle_IdList_StatusException() { GeofencingRemoveSingleOnSubscribe single = PowerMockito.spy(new GeofencingRemoveSingleOnSubscribe(rxLocation, geofenceRequestIds, null, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(false); when(geofencingApi.removeGeofences(apiClient, geofenceRequestIds)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertError(Single.create(single).test(), StatusException.class); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/GeofencingTest.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.GeofencingRequest; import com.google.android.gms.location.LocationServices; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.util.ArrayList; import java.util.List; import io.reactivex.Single; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; import static org.mockito.Mockito.times; @SuppressWarnings("MissingPermission") @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ Single.class, LocationServices.class, com.google.android.gms.location.ActivityRecognition.class, Status.class, ConnectionResult.class }) public class GeofencingTest extends BaseTest { @Mock GeofencingRequest geofencingRequest; @Mock PendingIntent pendingIntent; List geofenceRequestIds; @Override @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); PowerMockito.spy(Single.class); super.setup(); geofenceRequestIds = new ArrayList<>(0); } // Add @Test public void Add() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(GeofencingAddSingleOnSubscribe.class); rxLocation.geofencing().add(geofencingRequest, pendingIntent); rxLocation.geofencing().add(geofencingRequest, pendingIntent, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); GeofencingAddSingleOnSubscribe single = captor.getAllValues().get(0); assertEquals(geofencingRequest, single.geofencingRequest); assertEquals(pendingIntent, single.pendingIntent); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(geofencingRequest, single.geofencingRequest); assertEquals(pendingIntent, single.pendingIntent); assertTimeoutSet(single); } // Remove PendingIntent @Test public void Remove_PendingIntent() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(GeofencingRemoveSingleOnSubscribe.class); rxLocation.geofencing().remove(pendingIntent); rxLocation.geofencing().remove(pendingIntent, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); GeofencingRemoveSingleOnSubscribe single = captor.getAllValues().get(0); assertEquals(pendingIntent, single.pendingIntent); assertNull(single.geofenceRequestIds); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(pendingIntent, single.pendingIntent); assertNull(single.geofenceRequestIds); assertTimeoutSet(single); } @Test public void Remove_IdList() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(GeofencingRemoveSingleOnSubscribe.class); rxLocation.geofencing().remove(geofenceRequestIds); rxLocation.geofencing().remove(geofenceRequestIds, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); GeofencingRemoveSingleOnSubscribe single = captor.getAllValues().get(0); assertEquals(geofenceRequestIds, single.geofenceRequestIds); assertNull(single.pendingIntent); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(geofenceRequestIds, single.geofenceRequestIds); assertNull(single.pendingIntent); assertTimeoutSet(single); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/LocationOnSubscribeTest.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import android.location.Location; import android.os.Looper; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationAvailability; import com.google.android.gms.location.LocationListener; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; import io.reactivex.Maybe; import io.reactivex.Single; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Matchers.isNull; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; @SuppressWarnings("MissingPermission") @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ LocationRequest.class, LocationAvailability.class, LocationServices.class, com.google.android.gms.location.ActivityRecognition.class, Status.class, ConnectionResult.class, RxLocationBaseOnSubscribe.class }) public class LocationOnSubscribeTest extends BaseOnSubscribeTest { @Mock PendingIntent pendingIntent; @Mock LocationRequest locationRequest; @Mock Location location; @Override @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); super.setup(); } // LocationAvailabilitySingle @Test public void LocationAvailabilitySingle_Success() { LocationAvailabilitySingleOnSubscribe single = PowerMockito.spy(new LocationAvailabilitySingleOnSubscribe(rxLocation)); LocationAvailability locationAvailability = Mockito.mock(LocationAvailability.class); doReturn(true).when(locationAvailability).isLocationAvailable(); when(fusedLocationProviderApi.getLocationAvailability(apiClient)).thenReturn(locationAvailability); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), true); } @Test public void LocationAvailabilitySingle_Null_False() { LocationAvailabilitySingleOnSubscribe single = PowerMockito.spy(new LocationAvailabilitySingleOnSubscribe(rxLocation)); when(fusedLocationProviderApi.getLocationAvailability(apiClient)).thenReturn(null); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), false); } // LocationFlushSingle @Test public void LocationFlushSingle_Success() { LocationFlushSingleOnSubscribe single = PowerMockito.spy(new LocationFlushSingleOnSubscribe(rxLocation, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(true); when(fusedLocationProviderApi.flushLocations(apiClient)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), status); } @Test public void LocationFlushSingle_StatusException() { LocationFlushSingleOnSubscribe single = PowerMockito.spy(new LocationFlushSingleOnSubscribe(rxLocation, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(false); when(fusedLocationProviderApi.flushLocations(apiClient)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertError(Single.create(single).test(), StatusException.class); } // LocationLastMaybe @Test public void LocationLastMaybe_Success() { LocationLastMaybeOnSubscribe maybe = PowerMockito.spy(new LocationLastMaybeOnSubscribe(rxLocation)); Location location = Mockito.mock(Location.class); when(fusedLocationProviderApi.getLastLocation(apiClient)).thenReturn(location); setupBaseMaybeSuccess(maybe); assertSingleValue(Maybe.create(maybe).test(), location); } @Test public void LocationLastMaybe_Null_False() { LocationLastMaybeOnSubscribe maybe = PowerMockito.spy(new LocationLastMaybeOnSubscribe(rxLocation)); when(fusedLocationProviderApi.getLocationAvailability(apiClient)).thenReturn(null); setupBaseMaybeSuccess(maybe); assertNoValue(Maybe.create(maybe).test()); } // LocationRemoveUpdatesSingle @Test public void LocationRemoveUpdatesSingle_Success() { LocationRemoveUpdatesSingleOnSubscribe single = PowerMockito.spy(new LocationRemoveUpdatesSingleOnSubscribe(rxLocation, pendingIntent, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(true); when(fusedLocationProviderApi.removeLocationUpdates(apiClient, pendingIntent)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), status); } @Test public void LocationRemoveUpdatesSingle_StatusException() { LocationRemoveUpdatesSingleOnSubscribe single = PowerMockito.spy(new LocationRemoveUpdatesSingleOnSubscribe(rxLocation, pendingIntent, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(false); when(fusedLocationProviderApi.removeLocationUpdates(apiClient, pendingIntent)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertError(Single.create(single).test(), StatusException.class); } // LocationRequestUpdatesSingle @Test public void LocationRequestUpdatesSingle_Success() { LocationRequestUpdatesSingleOnSubscribe single = PowerMockito.spy(new LocationRequestUpdatesSingleOnSubscribe(rxLocation, locationRequest, pendingIntent, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(true); when(fusedLocationProviderApi.requestLocationUpdates(apiClient, locationRequest, pendingIntent)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), status); } @Test public void LocationRequestUpdatesSingle_StatusException() { LocationRequestUpdatesSingleOnSubscribe single = PowerMockito.spy(new LocationRequestUpdatesSingleOnSubscribe(rxLocation, locationRequest, pendingIntent, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(false); when(fusedLocationProviderApi.requestLocationUpdates(apiClient, locationRequest, pendingIntent)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertError(Single.create(single).test(), StatusException.class); } // LocationUpdatesFlowable @Test public void LocationUpdatesFlowable_Success() { LocationUpdatesFlowableOnSubscribe single = PowerMockito.spy(new LocationUpdatesFlowableOnSubscribe(rxLocation, locationRequest, null, null, null)); setPendingResultValue(status); doReturn(true).when(status).isSuccess(); doAnswer(invocation -> { single.locationListener.onLocationChanged(location); return pendingResult; }).when(fusedLocationProviderApi).requestLocationUpdates(eq(apiClient), eq(locationRequest), any(LocationListener.class), isNull(Looper.class)); setupBaseFlowableSuccess(single); Flowable.create(single, BackpressureStrategy.BUFFER).test() .assertValue(location) .assertNotTerminated(); } @Test public void LocationUpdatesFlowable_StatusException() { LocationUpdatesFlowableOnSubscribe single = PowerMockito.spy(new LocationUpdatesFlowableOnSubscribe(rxLocation, locationRequest, null, null, null)); setPendingResultValue(status); when(status.isSuccess()).thenReturn(false); doReturn(pendingResult).when(fusedLocationProviderApi).requestLocationUpdates(eq(apiClient), eq(locationRequest), any(LocationListener.class), isNull(Looper.class)); setupBaseFlowableSuccess(single); Flowable.create(single, BackpressureStrategy.BUFFER).test() .assertNoValues() .assertError(StatusException.class); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/LocationSettingsActivityTest.java ================================================ package com.patloew.rxlocation; import android.app.Activity; import android.content.Intent; import android.content.IntentSender; import com.google.android.gms.common.api.Status; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.util.UUID; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; import static org.powermock.api.mockito.PowerMockito.spy; @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ SettingsCheckHandleSingleOnSubscribe.class, Status.class }) public class LocationSettingsActivityTest { @Mock Status status; @Mock Intent intent; LocationSettingsActivity activity; final String observableId = UUID.randomUUID().toString(); @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); PowerMockito.spy(SettingsCheckHandleSingleOnSubscribe.class); activity = spy(new LocationSettingsActivity()); doReturn(observableId).when(intent).getStringExtra(LocationSettingsActivity.ARG_ID); doReturn(status).when(intent).getParcelableExtra(LocationSettingsActivity.ARG_STATUS); doReturn(intent).when(activity).getIntent(); } @Test public void onCreate() { activity.onCreate(null); doAnswer(invocation -> null).when(activity).handleIntent(); verify(activity).handleIntent(); } @Test public void onNewIntent() { activity.onNewIntent(intent); doAnswer(invocation -> null).when(activity).handleIntent(); verify(activity).setIntent(intent); verify(activity).handleIntent(); } @Test public void handleIntent() throws IntentSender.SendIntentException { activity.handleIntent(); verify(status).startResolutionForResult(activity, LocationSettingsActivity.REQUEST_CODE_RESOLUTION); } @Test public void handleIntent_SendIntentException() throws IntentSender.SendIntentException { doThrow(new IntentSender.SendIntentException()).when(status).startResolutionForResult(activity, LocationSettingsActivity.REQUEST_CODE_RESOLUTION); activity.handleIntent(); } @Test public void onActivityResult() { activity.onActivityResult(LocationSettingsActivity.REQUEST_CODE_RESOLUTION, Activity.RESULT_OK, null); verify(activity).setResolutionResultAndFinish(Activity.RESULT_OK); } @Test public void onActivityResult_wrongRequestCode() { activity.onActivityResult(-123, Activity.RESULT_OK, null); verify(activity).setResolutionResultAndFinish(Activity.RESULT_CANCELED); } @Test public void setResolutionResultAndFinish_OK() { activity.setResolutionResultAndFinish(Activity.RESULT_OK); PowerMockito.verifyStatic(SettingsCheckHandleSingleOnSubscribe.class); SettingsCheckHandleSingleOnSubscribe.onResolutionResult(observableId, Activity.RESULT_OK); verify(activity).finish(); } @Test public void setResolutionResultAndFinish_Canceled() { activity.setResolutionResultAndFinish(Activity.RESULT_CANCELED); PowerMockito.verifyStatic(SettingsCheckHandleSingleOnSubscribe.class); SettingsCheckHandleSingleOnSubscribe.onResolutionResult(observableId, Activity.RESULT_CANCELED); verify(activity).finish(); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/LocationTest.java ================================================ package com.patloew.rxlocation; import android.app.PendingIntent; import android.location.Location; import android.os.Looper; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; import io.reactivex.Maybe; import io.reactivex.Single; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.times; @SuppressWarnings("MissingPermission") @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ Flowable.class, Maybe.class, Single.class, Looper.class, LocationRequest.class, LocationServices.class, com.google.android.gms.location.ActivityRecognition.class, Status.class, ConnectionResult.class }) public class LocationTest extends BaseTest { @Mock PendingIntent pendingIntent; @Mock LocationRequest locationRequest; @Mock Location location; @Mock Looper looper; @Override @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); PowerMockito.spy(Single.class); PowerMockito.spy(Maybe.class); PowerMockito.spy(Flowable.class); super.setup(); } @Test public void Flush() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(LocationFlushSingleOnSubscribe.class); rxLocation.location().flush(); rxLocation.location().flush(TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); LocationFlushSingleOnSubscribe single = captor.getAllValues().get(0); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertTimeoutSet(single); } @Test public void LastLocation() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(LocationLastMaybeOnSubscribe.class); rxLocation.location().lastLocation(); PowerMockito.verifyStatic(Maybe.class, times(1)); Maybe.create(captor.capture()); LocationLastMaybeOnSubscribe single = captor.getAllValues().get(0); assertNoTimeoutSet(single); } @Test public void LocationAvailable() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(LocationAvailabilitySingleOnSubscribe.class); rxLocation.location().isLocationAvailable(); PowerMockito.verifyStatic(Single.class, times(1)); Single.create(captor.capture()); LocationAvailabilitySingleOnSubscribe single = captor.getAllValues().get(0); assertNoTimeoutSet(single); } @Test public void RequestUpdates() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(LocationRequestUpdatesSingleOnSubscribe.class); rxLocation.location().requestUpdates(locationRequest, pendingIntent); rxLocation.location().requestUpdates(locationRequest, pendingIntent, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); LocationRequestUpdatesSingleOnSubscribe single = captor.getAllValues().get(0); assertEquals(locationRequest, single.locationRequest); assertEquals(pendingIntent, single.pendingIntent); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(locationRequest, single.locationRequest); assertEquals(pendingIntent, single.pendingIntent); assertTimeoutSet(single); } @Test public void RemoveUpdates() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(LocationRemoveUpdatesSingleOnSubscribe.class); rxLocation.location().removeUpdates(pendingIntent); rxLocation.location().removeUpdates(pendingIntent, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); LocationRemoveUpdatesSingleOnSubscribe single = captor.getAllValues().get(0); assertEquals(pendingIntent, single.pendingIntent); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(pendingIntent, single.pendingIntent); assertTimeoutSet(single); } // Location Updates @Test public void LocationUpdates() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(LocationUpdatesFlowableOnSubscribe.class); rxLocation.location().updates(locationRequest); rxLocation.location().updates(locationRequest, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Flowable.class, times(2)); Flowable.create(captor.capture(), eq(BackpressureStrategy.MISSING)); LocationUpdatesFlowableOnSubscribe single = captor.getAllValues().get(0); assertEquals(locationRequest, single.locationRequest); assertNull(single.looper); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(locationRequest, single.locationRequest); assertNull(single.looper); assertTimeoutSet(single); } @Test public void LocationUpdates_Looper() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(LocationUpdatesFlowableOnSubscribe.class); rxLocation.location().updates(locationRequest, looper); rxLocation.location().updates(locationRequest, looper, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Flowable.class, times(2)); Flowable.create(captor.capture(), eq(BackpressureStrategy.MISSING)); LocationUpdatesFlowableOnSubscribe single = captor.getAllValues().get(0); assertEquals(locationRequest, single.locationRequest); assertEquals(looper, single.looper); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(locationRequest, single.locationRequest); assertEquals(looper, single.looper); assertTimeoutSet(single); } @Test public void LocationUpdates_BackpressureStrategy() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(LocationUpdatesFlowableOnSubscribe.class); rxLocation.location().updates(locationRequest, BackpressureStrategy.LATEST); rxLocation.location().updates(locationRequest, TIMEOUT_TIME, TIMEOUT_TIMEUNIT, BackpressureStrategy.LATEST); PowerMockito.verifyStatic(Flowable.class, times(2)); Flowable.create(captor.capture(), eq(BackpressureStrategy.LATEST)); LocationUpdatesFlowableOnSubscribe single = captor.getAllValues().get(0); assertEquals(locationRequest, single.locationRequest); assertNull(single.looper); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(locationRequest, single.locationRequest); assertNull(single.looper); assertTimeoutSet(single); } @Test public void LocationUpdates_Looper_BackpressureStrategy() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(LocationUpdatesFlowableOnSubscribe.class); rxLocation.location().updates(locationRequest, looper, BackpressureStrategy.LATEST); rxLocation.location().updates(locationRequest, looper, TIMEOUT_TIME, TIMEOUT_TIMEUNIT, BackpressureStrategy.LATEST); PowerMockito.verifyStatic(Flowable.class, times(2)); Flowable.create(captor.capture(), eq(BackpressureStrategy.LATEST)); LocationUpdatesFlowableOnSubscribe single = captor.getAllValues().get(0); assertEquals(locationRequest, single.locationRequest); assertEquals(looper, single.looper); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(locationRequest, single.locationRequest); assertEquals(looper, single.looper); assertTimeoutSet(single); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/RxLocationBaseOnSubscribeTest.java ================================================ package com.patloew.rxlocation; import android.support.v4.content.ContextCompat; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Scope; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.ActivityRecognition; import com.google.android.gms.location.LocationServices; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Matchers; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import static junit.framework.Assert.assertEquals; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ ContextCompat.class, Status.class, LocationServices.class, ActivityRecognition.class, ConnectionResult.class, GoogleApiClient.Builder.class }) public class RxLocationBaseOnSubscribeTest extends BaseOnSubscribeTest { @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); super.setup(); } @Test public void setupFitnessPendingResult_NoTimeout() { RxLocationBaseOnSubscribe rxLocationBaseOnSubscribe = spy(new RxLocationBaseOnSubscribe(rxLocation, null, null) { }); ResultCallback resultCallback = Mockito.mock(ResultCallback.class); rxLocationBaseOnSubscribe.setupLocationPendingResult(pendingResult, resultCallback); verify(pendingResult).setResultCallback(resultCallback); } @Test public void setupFitnessPendingResult_Timeout() { RxLocationBaseOnSubscribe rxLocationBaseOnSubscribe = spy(new RxLocationBaseOnSubscribe(rxLocation, TIMEOUT_TIME, TIMEOUT_TIMEUNIT) { }); ResultCallback resultCallback = Mockito.mock(ResultCallback.class); rxLocationBaseOnSubscribe.setupLocationPendingResult(pendingResult, resultCallback); verify(pendingResult).setResultCallback(resultCallback, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); } @Test public void createApiClient_NoScopes() { GoogleApiClient.Builder builder = Mockito.mock(GoogleApiClient.Builder.class); RxLocationBaseOnSubscribe rxLocationBaseOnSubscribe = spy(new RxLocationBaseOnSubscribe(ctx, new Api[]{ LocationServices.API }, null) { }); doReturn(builder).when(rxLocationBaseOnSubscribe).getApiClientBuilder(); doReturn(apiClient).when(builder).build(); RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = Mockito.mock(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class); assertEquals(apiClient, rxLocationBaseOnSubscribe.createApiClient(callbacks)); verify(builder).addApi(LocationServices.API); verify(builder).addConnectionCallbacks(callbacks); verify(builder).addOnConnectionFailedListener(callbacks); verify(builder, never()).addScope(Matchers.any(Scope.class)); verify(callbacks).setClient(Matchers.any(GoogleApiClient.class)); } @Test public void createApiClient_Scopes() { GoogleApiClient.Builder builder = Mockito.mock(GoogleApiClient.Builder.class); Scope scope = new Scope("Test"); RxLocationBaseOnSubscribe rxLocationBaseOnSubscribe = spy(new RxLocationBaseOnSubscribe(ctx, new Api[]{ LocationServices.API }, new Scope[]{ scope } ) { }); doReturn(builder).when(rxLocationBaseOnSubscribe).getApiClientBuilder(); doReturn(apiClient).when(builder).build(); RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = Mockito.mock(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class); assertEquals(apiClient, rxLocationBaseOnSubscribe.createApiClient(callbacks)); verify(builder).addApi(LocationServices.API); verify(builder).addScope(scope); verify(builder).addConnectionCallbacks(callbacks); verify(builder).addOnConnectionFailedListener(callbacks); verify(callbacks).setClient(Matchers.any(GoogleApiClient.class)); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/RxLocationFlowableOnSubscribeTest.java ================================================ package com.patloew.rxlocation; import android.support.v4.content.ContextCompat; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.ActivityRecognition; import com.google.android.gms.location.LocationServices; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Matchers; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; import io.reactivex.FlowableEmitter; import io.reactivex.subscribers.TestSubscriber; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ ContextCompat.class, LocationServices.class, ActivityRecognition.class, Status.class, ConnectionResult.class }) public class RxLocationFlowableOnSubscribeTest extends BaseOnSubscribeTest { @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); super.setup(); } @Test public void BaseObservable_ApiClient_Connected() { final Object object = new Object(); RxLocationFlowableOnSubscribe observable = spy(new RxLocationFlowableOnSubscribe(ctx, new Api[] {}, null) { @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, FlowableEmitter subscriber) { subscriber.onNext(object); subscriber.onComplete(); } }); doAnswer(invocation -> { RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = invocation.getArgument(0); callbacks.setClient(apiClient); callbacks.onConnected(null); return apiClient; }).when(observable).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); TestSubscriber sub = Flowable.create(observable, BackpressureStrategy.MISSING).test(); sub.assertValue(object); sub.assertComplete(); } @Test public void BaseObservable_ApiClient_Connected_Dispose() { final Object object = new Object(); RxLocationFlowableOnSubscribe observable = spy(new RxLocationFlowableOnSubscribe(ctx, new Api[] {}, null) { @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, FlowableEmitter subscriber) { subscriber.onNext(object); } }); doAnswer(invocation -> { RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = invocation.getArgument(0); callbacks.setClient(apiClient); callbacks.onConnected(null); return apiClient; }).when(observable).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); doReturn(true).when(apiClient).isConnected(); Flowable.create(observable, BackpressureStrategy.MISSING).subscribe().dispose(); verify(observable).onUnsubscribed(apiClient); verify(apiClient).disconnect(); } @Test public void BaseObservable_ApiClient_ConnectionSuspended() { final Object object = new Object(); RxLocationFlowableOnSubscribe observable = spy(new RxLocationFlowableOnSubscribe(ctx, new Api[] {}, null) { @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, FlowableEmitter subscriber) { subscriber.onNext(object); subscriber.onComplete(); } }); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = invocation.getArgument(0); callbacks.setClient(apiClient); callbacks.onConnectionSuspended(0); return apiClient; } }).when(observable).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); TestSubscriber sub = Flowable.create(observable, BackpressureStrategy.MISSING).test(); sub.assertNoValues(); sub.assertError(GoogleApiConnectionSuspendedException.class); } @Test public void BaseObservable_ApiClient_ConnectionFailed() { final Object object = new Object(); RxLocationFlowableOnSubscribe observable = spy(new RxLocationFlowableOnSubscribe(ctx, new Api[] {}, null) { @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, FlowableEmitter subscriber) { subscriber.onNext(object); subscriber.onComplete(); } }); doReturn(false).when(connectionResult).hasResolution(); doAnswer(invocation -> { RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = invocation.getArgument(0); callbacks.setClient(apiClient); callbacks.onConnectionFailed(connectionResult); return apiClient; }).when(observable).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); TestSubscriber sub = Flowable.create(observable, BackpressureStrategy.MISSING).test(); sub.assertNoValues(); sub.assertError(GoogleApiConnectionException.class); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/RxLocationMaybeOnSubscribeTest.java ================================================ package com.patloew.rxlocation; import android.support.v4.content.ContextCompat; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.ActivityRecognition; import com.google.android.gms.location.LocationServices; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Matchers; import org.mockito.MockitoAnnotations; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import io.reactivex.Maybe; import io.reactivex.MaybeEmitter; import io.reactivex.SingleEmitter; import io.reactivex.observers.TestObserver; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ ContextCompat.class, Status.class, LocationServices.class, ActivityRecognition.class, ConnectionResult.class, SingleEmitter.class }) public class RxLocationMaybeOnSubscribeTest extends BaseOnSubscribeTest { @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); super.setup(); } @Test public void ApiClient_Connected() { final Object object = new Object(); RxLocationMaybeOnSubscribe maybeOnSubscribe = spy(new RxLocationMaybeOnSubscribe(ctx, new Api[] {}, null) { @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, MaybeEmitter emitter) { emitter.onSuccess(object); } }); doAnswer(invocation -> { RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = invocation.getArgument(0); callbacks.setClient(apiClient); callbacks.onConnected(null); return apiClient; }).when(maybeOnSubscribe).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); TestObserver sub = Maybe.create(maybeOnSubscribe).test(); sub.assertValue(object); sub.assertComplete(); } @Test public void ApiClient_Connected_Dispose() { RxLocationMaybeOnSubscribe maybeOnSubscribe = spy(new RxLocationMaybeOnSubscribe(ctx, new Api[] {}, null) { @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, MaybeEmitter emitter) { } }); doAnswer(invocation -> { RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = invocation.getArgument(0); callbacks.setClient(apiClient); callbacks.onConnected(null); return apiClient; }).when(maybeOnSubscribe).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); doReturn(true).when(apiClient).isConnected(); Maybe.create(maybeOnSubscribe).subscribe().dispose(); verify(maybeOnSubscribe).onUnsubscribed(apiClient); verify(apiClient).disconnect(); } @Test public void ApiClient_ConnectionSuspended() { final Object object = new Object(); RxLocationMaybeOnSubscribe maybeOnSubscribe = spy(new RxLocationMaybeOnSubscribe(ctx, new Api[] {}, null) { @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, MaybeEmitter emitter) { emitter.onSuccess(object); } }); doAnswer(invocation -> { RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = invocation.getArgument(0); callbacks.setClient(apiClient); callbacks.onConnectionSuspended(0); return apiClient; }).when(maybeOnSubscribe).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); TestObserver sub = Maybe.create(maybeOnSubscribe).test(); sub.assertNoValues(); sub.assertError(GoogleApiConnectionSuspendedException.class); } @Test public void ApiClient_ConnectionFailed() { final Object object = new Object(); RxLocationMaybeOnSubscribe maybeOnSubscribe = spy(new RxLocationMaybeOnSubscribe(ctx, new Api[] {}, null) { @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, MaybeEmitter emitter) { emitter.onSuccess(object); } }); doReturn(false).when(connectionResult).hasResolution(); doAnswer(invocation -> { RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = invocation.getArgument(0); callbacks.setClient(apiClient); callbacks.onConnectionFailed(connectionResult); return apiClient; }).when(maybeOnSubscribe).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); TestObserver sub = Maybe.create(maybeOnSubscribe).test(); sub.assertNoValues(); sub.assertError(GoogleApiConnectionException.class); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/RxLocationSingleOnSubscribeTest.java ================================================ package com.patloew.rxlocation; import android.support.v4.content.ContextCompat; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.ActivityRecognition; import com.google.android.gms.location.LocationServices; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Matchers; import org.mockito.MockitoAnnotations; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import io.reactivex.Single; import io.reactivex.SingleEmitter; import io.reactivex.observers.TestObserver; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ ContextCompat.class, Status.class, LocationServices.class, ActivityRecognition.class, ConnectionResult.class, SingleEmitter.class }) public class RxLocationSingleOnSubscribeTest extends BaseOnSubscribeTest { @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); super.setup(); } @Test public void ApiClient_Connected() { final Object object = new Object(); RxLocationSingleOnSubscribe single = spy(new RxLocationSingleOnSubscribe(ctx, new Api[] {}, null) { @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { emitter.onSuccess(object); } }); doAnswer(invocation -> { RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = invocation.getArgument(0); callbacks.setClient(apiClient); callbacks.onConnected(null); return apiClient; }).when(single).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); TestObserver sub = Single.create(single).test(); sub.assertValue(object); sub.assertComplete(); } @Test public void ApiClient_Connected_Dispose() { RxLocationSingleOnSubscribe single = spy(new RxLocationSingleOnSubscribe(ctx, new Api[] {}, null) { @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { } }); doAnswer(invocation -> { RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = invocation.getArgument(0); callbacks.setClient(apiClient); callbacks.onConnected(null); return apiClient; }).when(single).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); doReturn(true).when(apiClient).isConnected(); Single.create(single).subscribe().dispose(); verify(single).onUnsubscribed(apiClient); verify(apiClient).disconnect(); } @Test public void ApiClient_ConnectionSuspended() { final Object object = new Object(); RxLocationSingleOnSubscribe single = spy(new RxLocationSingleOnSubscribe(ctx, new Api[] {}, null) { @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { emitter.onSuccess(object); } }); doAnswer(invocation -> { RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = invocation.getArgument(0); callbacks.setClient(apiClient); callbacks.onConnectionSuspended(0); return apiClient; }).when(single).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); TestObserver sub = Single.create(single).test(); sub.assertNoValues(); sub.assertError(GoogleApiConnectionSuspendedException.class); } @Test public void ApiClient_ConnectionFailed() { final Object object = new Object(); RxLocationSingleOnSubscribe single = spy(new RxLocationSingleOnSubscribe(ctx, new Api[] {}, null) { @Override protected void onGoogleApiClientReady(GoogleApiClient apiClient, SingleEmitter emitter) { emitter.onSuccess(object); } }); doReturn(false).when(connectionResult).hasResolution(); doAnswer(invocation -> { RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks callbacks = invocation.getArgument(0); callbacks.setClient(apiClient); callbacks.onConnectionFailed(connectionResult); return apiClient; }).when(single).createApiClient(Matchers.any(RxLocationBaseOnSubscribe.ApiClientConnectionCallbacks.class)); TestObserver sub = Single.create(single).test(); sub.assertNoValues(); sub.assertError(GoogleApiConnectionException.class); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/RxLocationTest.java ================================================ package com.patloew.rxlocation; import android.support.v4.content.ContextCompat; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Api; import com.google.android.gms.common.api.Scope; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.ActivityRecognition; import com.google.android.gms.location.LocationServices; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; import io.reactivex.Observable; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ Observable.class, ContextCompat.class, LocationServices.class, ActivityRecognition.class, Status.class, ConnectionResult.class, RxLocationBaseOnSubscribe.class }) public class RxLocationTest extends BaseOnSubscribeTest { @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); super.setup(); } // RxFit @Test public void setTimeout() { rxLocation.setDefaultTimeout(TIMEOUT_TIME, TIMEOUT_TIMEUNIT); assertEquals(TIMEOUT_TIME, (long) rxLocation.timeoutTime); assertEquals(TIMEOUT_TIMEUNIT, rxLocation.timeoutUnit); } @Test(expected = IllegalArgumentException.class) public void setTimeout_TimeUnitMissing() { rxLocation.setDefaultTimeout(TIMEOUT_TIME, null); assertNull(rxLocation.timeoutTime); assertNull(rxLocation.timeoutUnit); } @Test public void resetDefaultTimeout() { rxLocation.setDefaultTimeout(TIMEOUT_TIME, TIMEOUT_TIMEUNIT); rxLocation.resetDefaultTimeout(); assertNull(rxLocation.timeoutTime); assertNull(rxLocation.timeoutUnit); } // GoogleApiClientObservable @Test public void GoogleAPIClientObservable_Success() { GoogleApiClientFlowable single = PowerMockito.spy(new GoogleApiClientFlowable(ctx, new Api[]{}, new Scope[]{})); setupBaseFlowableSuccess(single); Flowable.create(single, BackpressureStrategy.LATEST).test().assertValue(apiClient); } @Test public void GoogleAPIClientObservable_ConnectionException() { final GoogleApiClientFlowable single = PowerMockito.spy(new GoogleApiClientFlowable(ctx, new Api[]{}, new Scope[]{})); setupBaseFlowableError(single); assertError(Flowable.create(single, BackpressureStrategy.LATEST).test(), GoogleApiConnectionException.class); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/SettingsOnSubscribeTest.java ================================================ package com.patloew.rxlocation; import android.app.Activity; import android.content.Intent; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.CommonStatusCodes; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationAvailability; import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.LocationSettingsRequest; import com.google.android.gms.location.LocationSettingsResult; import com.google.android.gms.location.LocationSettingsStatusCodes; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import io.reactivex.Single; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.when; @SuppressWarnings("MissingPermission") @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ LocationSettingsRequest.class, LocationSettingsResult.class, LocationAvailability.class, LocationServices.class, com.google.android.gms.location.ActivityRecognition.class, Status.class, ConnectionResult.class, RxLocationBaseOnSubscribe.class }) public class SettingsOnSubscribeTest extends BaseOnSubscribeTest { @Mock LocationSettingsRequest locationSettingsRequest; @Mock LocationSettingsResult locationSettingsResult; @Override @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); super.setup(); } // SettingsCheckSingle @Test public void SettingsCheckSingle_Success() { SettingsCheckSingleOnSubscribe single = PowerMockito.spy(new SettingsCheckSingleOnSubscribe(rxLocation, locationSettingsRequest, null, null)); setPendingResultValue(locationSettingsResult); doReturn(status).when(locationSettingsResult).getStatus(); doReturn(true).when(status).isSuccess(); when(settingsApi.checkLocationSettings(apiClient, locationSettingsRequest)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), locationSettingsResult); } @Test public void SettingsCheckSingle_StatusException() { SettingsCheckSingleOnSubscribe single = PowerMockito.spy(new SettingsCheckSingleOnSubscribe(rxLocation, locationSettingsRequest, null, null)); setPendingResultValue(locationSettingsResult); doReturn(status).when(locationSettingsResult).getStatus(); doReturn(false).when(status).isSuccess(); when(settingsApi.checkLocationSettings(apiClient, locationSettingsRequest)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertError(Single.create(single).test(), StatusException.class); } // SettingsCheckHandleSingle @Test public void SettingsCheckHandleSingle_Success() { SettingsCheckHandleSingleOnSubscribe single = PowerMockito.spy(new SettingsCheckHandleSingleOnSubscribe(rxLocation, locationSettingsRequest, null, null)); setPendingResultValue(locationSettingsResult); doReturn(status).when(locationSettingsResult).getStatus(); doReturn(LocationSettingsStatusCodes.SUCCESS).when(status).getStatusCode(); when(settingsApi.checkLocationSettings(apiClient, locationSettingsRequest)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), true); } @Test public void SettingsCheckHandleSingle_ChangeUnavailable() { SettingsCheckHandleSingleOnSubscribe single = PowerMockito.spy(new SettingsCheckHandleSingleOnSubscribe(rxLocation, locationSettingsRequest, null, null)); setPendingResultValue(locationSettingsResult); doReturn(status).when(locationSettingsResult).getStatus(); doReturn(LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE).when(status).getStatusCode(); when(settingsApi.checkLocationSettings(apiClient, locationSettingsRequest)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), false); } @Test public void SettingsCheckHandleSingle_ResolutionRequired_Success() { SettingsCheckHandleSingleOnSubscribe single = PowerMockito.spy(new SettingsCheckHandleSingleOnSubscribe(rxLocation, locationSettingsRequest, null, null)); setPendingResultValue(locationSettingsResult); doReturn(status).when(locationSettingsResult).getStatus(); doReturn(LocationSettingsStatusCodes.RESOLUTION_REQUIRED).when(status).getStatusCode(); when(settingsApi.checkLocationSettings(apiClient, locationSettingsRequest)).thenReturn(pendingResult); doAnswer(invocation -> { String key = (String) SettingsCheckHandleSingleOnSubscribe.observableMap.keySet().toArray()[0]; SettingsCheckHandleSingleOnSubscribe.onResolutionResult(key, Activity.RESULT_OK); return null; }).when(ctx).startActivity(any(Intent.class)); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), true); assertTrue(SettingsCheckHandleSingleOnSubscribe.observableMap.isEmpty()); } @Test public void SettingsCheckHandleSingle_ResolutionRequired_Canceled() { SettingsCheckHandleSingleOnSubscribe single = PowerMockito.spy(new SettingsCheckHandleSingleOnSubscribe(rxLocation, locationSettingsRequest, null, null)); setPendingResultValue(locationSettingsResult); doReturn(status).when(locationSettingsResult).getStatus(); doReturn(LocationSettingsStatusCodes.RESOLUTION_REQUIRED).when(status).getStatusCode(); when(settingsApi.checkLocationSettings(apiClient, locationSettingsRequest)).thenReturn(pendingResult); doAnswer(invocation -> { String key = (String) SettingsCheckHandleSingleOnSubscribe.observableMap.keySet().toArray()[0]; SettingsCheckHandleSingleOnSubscribe.onResolutionResult(key, Activity.RESULT_CANCELED); return null; }).when(ctx).startActivity(any(Intent.class)); setupBaseSingleSuccess(single); assertSingleValue(Single.create(single).test(), false); assertTrue(SettingsCheckHandleSingleOnSubscribe.observableMap.isEmpty()); } @Test public void SettingsCheckHandleSingle_StatusException() { SettingsCheckHandleSingleOnSubscribe single = PowerMockito.spy(new SettingsCheckHandleSingleOnSubscribe(rxLocation, locationSettingsRequest, null, null)); setPendingResultValue(locationSettingsResult); doReturn(status).when(locationSettingsResult).getStatus(); doReturn(CommonStatusCodes.TIMEOUT).when(status).getStatusCode(); when(settingsApi.checkLocationSettings(apiClient, locationSettingsRequest)).thenReturn(pendingResult); setupBaseSingleSuccess(single); assertError(Single.create(single).test(), StatusException.class); } } ================================================ FILE: library/src/test/java/com/patloew/rxlocation/SettingsTest.java ================================================ package com.patloew.rxlocation; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.LocationSettingsRequest; import com.google.android.gms.location.LocationSettingsResult; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; import org.powermock.modules.junit4.PowerMockRunner; import io.reactivex.Single; import static junit.framework.Assert.assertEquals; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.powermock.api.mockito.PowerMockito.spy; @SuppressWarnings("MissingPermission") @RunWith(PowerMockRunner.class) @PrepareOnlyThisForTest({ Single.class, LocationRequest.class, LocationSettingsRequest.Builder.class, LocationSettingsRequest.class, LocationSettingsResult.class, LocationServices.class, com.google.android.gms.location.ActivityRecognition.class, Status.class, ConnectionResult.class }) public class SettingsTest extends BaseTest { @Mock LocationSettingsRequest.Builder locationSettingsRequestBuilder; @Mock LocationSettingsRequest locationSettingsRequest; @Mock LocationSettingsResult locationSettingsResult; @Mock LocationRequest locationRequest; @Override @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); spy(Single.class); super.setup(); } @Test public void Check_LocationRequest() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(SettingsCheckSingleOnSubscribe.class); doReturn(locationSettingsRequestBuilder).when(locationSettingsRequestBuilder).addLocationRequest(locationRequest); doReturn(locationSettingsRequest).when(locationSettingsRequestBuilder).build(); LocationSettings settings = spy(rxLocation.settings()); doReturn(locationSettingsRequestBuilder).when(settings).getLocationSettingsRequestBuilder(); settings.check(locationRequest); settings.check(locationRequest, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); SettingsCheckSingleOnSubscribe single = captor.getAllValues().get(0); assertEquals(locationSettingsRequest, single.locationSettingsRequest); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(locationSettingsRequest, single.locationSettingsRequest); assertTimeoutSet(single); } @Test public void Check_LocationSettingsRequest() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(SettingsCheckSingleOnSubscribe.class); rxLocation.settings().check(locationSettingsRequest); rxLocation.settings().check(locationSettingsRequest, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); SettingsCheckSingleOnSubscribe single = captor.getAllValues().get(0); assertEquals(locationSettingsRequest, single.locationSettingsRequest); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(locationSettingsRequest, single.locationSettingsRequest); assertTimeoutSet(single); } @Test public void CheckAndHandleResolution_Completable_LocationRequest() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(SettingsCheckHandleSingleOnSubscribe.class); doReturn(locationSettingsRequestBuilder).when(locationSettingsRequestBuilder).addLocationRequest(locationRequest); doReturn(locationSettingsRequest).when(locationSettingsRequestBuilder).build(); LocationSettings settings = spy(rxLocation.settings()); doReturn(locationSettingsRequestBuilder).when(settings).getLocationSettingsRequestBuilder(); settings.checkAndHandleResolutionCompletable(locationRequest); settings.checkAndHandleResolutionCompletable(locationRequest, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); SettingsCheckHandleSingleOnSubscribe single = captor.getAllValues().get(0); assertEquals(locationSettingsRequest, single.locationSettingsRequest); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(locationSettingsRequest, single.locationSettingsRequest); assertTimeoutSet(single); } @Test public void CheckAndHandleResolution_LocationRequest() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(SettingsCheckHandleSingleOnSubscribe.class); doReturn(locationSettingsRequestBuilder).when(locationSettingsRequestBuilder).addLocationRequest(locationRequest); doReturn(locationSettingsRequest).when(locationSettingsRequestBuilder).build(); LocationSettings settings = spy(rxLocation.settings()); doReturn(locationSettingsRequestBuilder).when(settings).getLocationSettingsRequestBuilder(); settings.checkAndHandleResolution(locationRequest); settings.checkAndHandleResolution(locationRequest, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); SettingsCheckHandleSingleOnSubscribe single = captor.getAllValues().get(0); assertEquals(locationSettingsRequest, single.locationSettingsRequest); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(locationSettingsRequest, single.locationSettingsRequest); assertTimeoutSet(single); } @Test public void CheckAndHandleResolution_Completable_LocationSettingsRequest() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(SettingsCheckHandleSingleOnSubscribe.class); rxLocation.settings().checkAndHandleResolutionCompletable(locationSettingsRequest); rxLocation.settings().checkAndHandleResolutionCompletable(locationSettingsRequest, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); SettingsCheckHandleSingleOnSubscribe single = captor.getAllValues().get(0); assertEquals(locationSettingsRequest, single.locationSettingsRequest); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(locationSettingsRequest, single.locationSettingsRequest); assertTimeoutSet(single); } @Test public void CheckAndHandleResolution_LocationSettingsRequest() throws Exception { ArgumentCaptor captor = ArgumentCaptor.forClass(SettingsCheckHandleSingleOnSubscribe.class); rxLocation.settings().checkAndHandleResolution(locationSettingsRequest); rxLocation.settings().checkAndHandleResolution(locationSettingsRequest, TIMEOUT_TIME, TIMEOUT_TIMEUNIT); PowerMockito.verifyStatic(Single.class, times(2)); Single.create(captor.capture()); SettingsCheckHandleSingleOnSubscribe single = captor.getAllValues().get(0); assertEquals(locationSettingsRequest, single.locationSettingsRequest); assertNoTimeoutSet(single); single = captor.getAllValues().get(1); assertEquals(locationSettingsRequest, single.locationSettingsRequest); assertTimeoutSet(single); } } ================================================ FILE: sample/.gitignore ================================================ /build ================================================ FILE: sample/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion 28 defaultConfig { applicationId "com.patloew.rxlocationsample" minSdkVersion 14 targetSdkVersion 22 versionCode 1 versionName "1.0.0" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } lintOptions { abortOnError false } testOptions { unitTests.returnDefaultValues = true } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:27.1.1' implementation 'com.android.support:support-v4:27.1.1' implementation "com.android.support:design:27.1.1" implementation "com.android.support:cardview-v7:27.1.1" implementation project(':library') //implementation 'com.patloew.rxlocation:rxlocation:1.0.5' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' implementation 'io.reactivex.rxjava2:rxjava:2.1.14' implementation 'com.google.android.gms:play-services-location:15.0.1' implementation('com.mikepenz:aboutlibraries:6.0.9@aar') { transitive = true } debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4' releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.18.3' } ================================================ FILE: sample/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/patricklowenstein/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: sample/src/main/AndroidManifest.xml ================================================ ================================================ FILE: sample/src/main/java/com/patloew/rxlocationsample/MainActivity.java ================================================ package com.patloew.rxlocationsample; import android.location.Address; import android.location.Location; import android.os.Bundle; import android.support.design.widget.Snackbar; import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; import android.widget.TextView; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GoogleApiAvailability; import com.mikepenz.aboutlibraries.Libs; import com.mikepenz.aboutlibraries.LibsBuilder; import com.patloew.rxlocation.RxLocation; import java.text.DateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class MainActivity extends AppCompatActivity implements MainView { private static final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance(); private TextView lastUpdate; private TextView locationText; private TextView addressText; private RxLocation rxLocation; private MainPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); lastUpdate = findViewById(R.id.tv_last_update); locationText = findViewById(R.id.tv_current_location); addressText = findViewById(R.id.tv_current_address); rxLocation = new RxLocation(this); rxLocation.setDefaultTimeout(15, TimeUnit.SECONDS); presenter = new MainPresenter(rxLocation); } @Override protected void onStart() { super.onStart(); presenter.attachView(this); } @Override protected void onResume() { super.onResume(); checkPlayServicesAvailable(); } private void checkPlayServicesAvailable() { final GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance(); final int status = apiAvailability.isGooglePlayServicesAvailable(this); if(status != ConnectionResult.SUCCESS) { if(apiAvailability.isUserResolvableError(status)) { apiAvailability.getErrorDialog(this, status, 1).show(); } else { Snackbar.make(lastUpdate, "Google Play Services unavailable. This app will not work", Snackbar.LENGTH_INDEFINITE).show(); } } } @Override protected void onStop() { super.onStop(); presenter.detachView(); } @Override protected void onDestroy() { super.onDestroy(); MyApplication.getRefWatcher().watch(presenter); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { if(item.getItemId() == R.id.menu_licenses) { new LibsBuilder() .withFields(Libs.toStringArray(R.string.class.getFields())) .withActivityStyle(Libs.ActivityStyle.LIGHT_DARK_TOOLBAR) .withActivityTitle("Open Source Licenses") .withLicenseShown(true) .start(this); return true; } return false; } // View Interface @Override public void onLocationUpdate(Location location) { lastUpdate.setText(DATE_FORMAT.format(new Date())); locationText.setText(location.getLatitude() + ", " + location.getLongitude()); } @Override public void onAddressUpdate(Address address) { addressText.setText(getAddressText(address)); } @Override public void onLocationSettingsUnsuccessful() { Snackbar.make(lastUpdate, "Location settings requirements not satisfied. Showing last known location if available.", Snackbar.LENGTH_INDEFINITE) .setAction("Retry", view -> presenter.startLocationRefresh()) .show(); } private String getAddressText(Address address) { String addressText = ""; final int maxAddressLineIndex = address.getMaxAddressLineIndex(); for(int i=0; i<=maxAddressLineIndex; i++) { addressText += address.getAddressLine(i); if(i != maxAddressLineIndex) { addressText += "\n"; } } return addressText; } } ================================================ FILE: sample/src/main/java/com/patloew/rxlocationsample/MainPresenter.java ================================================ package com.patloew.rxlocationsample; import android.location.Address; import android.location.Location; import android.util.Log; import com.google.android.gms.location.LocationRequest; import com.patloew.rxlocation.RxLocation; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public class MainPresenter { private final CompositeDisposable disposable = new CompositeDisposable(); private final RxLocation rxLocation; private final LocationRequest locationRequest; private MainView view; public MainPresenter(RxLocation rxLocation) { this.rxLocation = rxLocation; this.locationRequest = LocationRequest.create() .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY) .setInterval(5000); } public void attachView(MainView view) { this.view = view; startLocationRefresh(); } public void detachView() { this.view = null; disposable.clear(); } public void startLocationRefresh() { disposable.add( rxLocation.settings().checkAndHandleResolution(locationRequest) .flatMapObservable(this::getAddressObservable) .observeOn(AndroidSchedulers.mainThread()) .subscribe(view::onAddressUpdate, throwable -> Log.e("MainPresenter", "Error fetching location/address updates", throwable)) ); } private Observable
getAddressObservable(boolean success) { if(success) { return rxLocation.location().updates(locationRequest) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .doOnNext(view::onLocationUpdate) .flatMap(this::getAddressFromLocation); } else { view.onLocationSettingsUnsuccessful(); return rxLocation.location().lastLocation() .doOnSuccess(view::onLocationUpdate) .flatMapObservable(this::getAddressFromLocation); } } private Observable
getAddressFromLocation(Location location) { return rxLocation.geocoding().fromLocation(location).toObservable() .subscribeOn(Schedulers.io()); } } ================================================ FILE: sample/src/main/java/com/patloew/rxlocationsample/MainView.java ================================================ package com.patloew.rxlocationsample; import android.location.Address; import android.location.Location; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ public interface MainView { void onLocationUpdate(Location location); void onAddressUpdate(Address address); void onLocationSettingsUnsuccessful(); } ================================================ FILE: sample/src/main/java/com/patloew/rxlocationsample/MyApplication.java ================================================ package com.patloew.rxlocationsample; import android.app.Application; import com.squareup.leakcanary.LeakCanary; import com.squareup.leakcanary.RefWatcher; public class MyApplication extends Application { private static RefWatcher refWatcher; @Override public void onCreate() { super.onCreate(); refWatcher = LeakCanary.install(this); } public static RefWatcher getRefWatcher() { return refWatcher; } } ================================================ FILE: sample/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: sample/src/main/res/menu/menu.xml ================================================ ================================================ FILE: sample/src/main/res/values/colors.xml ================================================ #3F51B5 #303F9F #FF4081 ================================================ FILE: sample/src/main/res/values/dimens.xml ================================================ 16dp 16dp ================================================ FILE: sample/src/main/res/values/strings.xml ================================================ RxLocation Sample ================================================ FILE: sample/src/main/res/values/styles.xml ================================================ ================================================ FILE: sample/src/main/res/values-w820dp/dimens.xml ================================================ 64dp ================================================ FILE: sample/src/test/java/com/patloew/rxlocationsample/MainPresenterTest.java ================================================ package com.patloew.rxlocationsample; import android.location.Address; import android.location.Location; import com.google.android.gms.location.LocationRequest; import com.patloew.rxlocation.FusedLocation; import com.patloew.rxlocation.Geocoding; import com.patloew.rxlocation.RxLocation; import com.patloew.rxlocation.LocationSettings; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.Matchers; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.Arrays; import io.reactivex.Maybe; import io.reactivex.Observable; import io.reactivex.Single; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; /* Copyright 2016 Patrick Löwenstein * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ @RunWith(JUnit4.class) public class MainPresenterTest { @Rule public RxSchedulersOverrideRule rxSchedulersOverrideRule = new RxSchedulersOverrideRule(); @Mock RxLocation rxLocation; @Mock FusedLocation fusedLocation; @Mock LocationSettings locationSettings; @Mock Geocoding geocoding; @Mock MainView mainView; MainPresenter mainPresenter; @Before public void setup() { MockitoAnnotations.initMocks(this); doReturn(fusedLocation).when(rxLocation).location(); doReturn(locationSettings).when(rxLocation).settings(); doReturn(geocoding).when(rxLocation).geocoding(); mainPresenter = new MainPresenter(rxLocation); } @Test public void settingsSatisfied_1Location() { Location loc = Mockito.mock(Location.class); Address address = Mockito.mock(Address.class); doReturn(Single.just(true)).when(locationSettings).checkAndHandleResolution(Matchers.any(LocationRequest.class)); doReturn(Observable.just(loc)).when(fusedLocation).updates(Matchers.any(LocationRequest.class)); doReturn(Maybe.just(address)).when(geocoding).fromLocation(Matchers.any(android.location.Location.class)); mainPresenter.attachView(mainView); verify(mainView).onLocationUpdate(loc); verify(mainView).onAddressUpdate(address); verifyNoMoreInteractions(mainView); } @Test public void settingsNotSatisfied_LastLocation() { Location loc = Mockito.mock(Location.class); Address address = Mockito.mock(Address.class); doReturn(Single.just(false)).when(locationSettings).checkAndHandleResolution(Matchers.any(LocationRequest.class)); doReturn(Maybe.just(loc)).when(fusedLocation).lastLocation(); doReturn(Maybe.just(address)).when(geocoding).fromLocation(Matchers.any(android.location.Location.class)); mainPresenter.attachView(mainView); verify(mainView).onLocationSettingsUnsuccessful(); verify(mainView).onLocationUpdate(loc); verify(mainView).onAddressUpdate(address); verifyNoMoreInteractions(mainView); } @Test public void settingsNotSatisfied_NoLastLocation() { doReturn(Single.just(false)).when(locationSettings).checkAndHandleResolution(Matchers.any(LocationRequest.class)); doReturn(Maybe.empty()).when(fusedLocation).lastLocation(); mainPresenter.attachView(mainView); verify(mainView).onLocationSettingsUnsuccessful(); verifyNoMoreInteractions(mainView); } } ================================================ FILE: sample/src/test/java/com/patloew/rxlocationsample/RxSchedulersOverrideRule.java ================================================ package com.patloew.rxlocationsample; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; import io.reactivex.android.plugins.RxAndroidPlugins; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; /* Copyright 2015 Ribot Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ public class RxSchedulersOverrideRule implements TestRule { @Override public Statement apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { RxAndroidPlugins.reset(); RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline()); RxJavaPlugins.reset(); RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline()); RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline()); base.evaluate(); RxAndroidPlugins.reset(); RxJavaPlugins.reset(); } }; } } ================================================ FILE: settings.gradle ================================================ include ':sample', ':library'