diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..6ba5e700
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+.gradle
+/.idea/
+/local.properties
+/.idea/workspace.xml
+/build
+.DS_Store
+*.iml
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 00000000..95971647
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,16 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.7.+'
+ }
+}
+
+allprojects {
+ repositories {
+ mavenCentral()
+ }
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..5d08ba75
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,18 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Settings specified in this file will override any Gradle settings
+# configured through the IDE.
+
+# 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
+
+# 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
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..8c0fb64a
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..9b8ffdd4
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 00000000..91a7e269
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# 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
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# 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\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+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" ] ; 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, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ # 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=$((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
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 00000000..8a0b282a
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@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
+
+@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=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@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 init
+
+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 init
+
+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
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+: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 %CMD_LINE_ARGS%
+
+: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
diff --git a/library/.gitignore b/library/.gitignore
new file mode 100644
index 00000000..e69de29b
diff --git a/library/build.gradle b/library/build.gradle
new file mode 100644
index 00000000..c0c871a8
--- /dev/null
+++ b/library/build.gradle
@@ -0,0 +1,21 @@
+apply plugin: 'android-library'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "19.0.1"
+
+ defaultConfig {
+ minSdkVersion 9
+ targetSdkVersion 19
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+}
+
+dependencies {
+ compile 'com.google.android.gms:play-services:4.1.32'
+ compile 'com.netflix.rxjava:rxjava-android:0.16.1'
+}
\ No newline at end of file
diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..cf0d1805
--- /dev/null
+++ b/library/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/LocationConnectionException.java b/library/src/main/java/pl/charmas/android/reactivelocation/LocationConnectionException.java
new file mode 100644
index 00000000..564bbf86
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/LocationConnectionException.java
@@ -0,0 +1,16 @@
+package pl.charmas.android.reactivelocation;
+
+import com.google.android.gms.common.ConnectionResult;
+
+public class LocationConnectionException extends RuntimeException {
+ private final ConnectionResult connectionResult;
+
+ public LocationConnectionException(String detailMessage, ConnectionResult connectionResult) {
+ super(detailMessage);
+ this.connectionResult = connectionResult;
+ }
+
+ public ConnectionResult getConnectionResult() {
+ return connectionResult;
+ }
+}
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/ReactiveLocationProvider.java b/library/src/main/java/pl/charmas/android/reactivelocation/ReactiveLocationProvider.java
new file mode 100644
index 00000000..14d33144
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/ReactiveLocationProvider.java
@@ -0,0 +1,54 @@
+package pl.charmas.android.reactivelocation;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.location.Address;
+import android.location.Location;
+
+import com.google.android.gms.location.Geofence;
+import com.google.android.gms.location.LocationRequest;
+
+import java.util.List;
+
+import pl.charmas.android.reactivelocation.observables.GeodecodeObservable;
+import pl.charmas.android.reactivelocation.observables.LastKnownLocationObservable;
+import pl.charmas.android.reactivelocation.observables.LocationUpdatesObservable;
+import pl.charmas.android.reactivelocation.observables.geofence.AddGeofenceObservable;
+import pl.charmas.android.reactivelocation.observables.geofence.RemoveGeofenceObservable;
+import pl.charmas.android.reactivelocation.observables.geofence.RemoveGeofencesResult;
+import rx.Observable;
+
+public class ReactiveLocationProvider {
+
+ private final Context ctx;
+
+ public ReactiveLocationProvider(Context ctx) {
+ this.ctx = ctx;
+ }
+
+ public Observable getLastKnownLocation() {
+ return LastKnownLocationObservable.createObservable(ctx);
+ }
+
+ public Observable getUpdatedLocation(LocationRequest locationRequest) {
+ return LocationUpdatesObservable.createObservable(ctx, locationRequest);
+ }
+
+ public Observable> getGeocodeObservable(double lat, double lng, int maxResults) {
+ return GeodecodeObservable.createObservable(ctx, lat, lng, maxResults);
+ }
+
+ public Observable addGeofences(PendingIntent geofenceTransitionPendingIntent, List geofences) {
+ return AddGeofenceObservable.createObservable(ctx, geofences, geofenceTransitionPendingIntent);
+ }
+
+ public Observable removeGeofences(PendingIntent pendingIntent) {
+ return RemoveGeofenceObservable.createObservable(ctx, pendingIntent);
+ }
+
+ public Observable removeGeofences(List requestIds) {
+ return RemoveGeofenceObservable.createObservable(ctx, requestIds);
+ }
+
+
+}
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/observables/BaseLocationObservable.java b/library/src/main/java/pl/charmas/android/reactivelocation/observables/BaseLocationObservable.java
new file mode 100644
index 00000000..ce8601f6
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/observables/BaseLocationObservable.java
@@ -0,0 +1,81 @@
+package pl.charmas.android.reactivelocation.observables;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.location.LocationClient;
+
+import pl.charmas.android.reactivelocation.LocationConnectionException;
+import rx.Observable;
+import rx.Observer;
+import rx.Subscription;
+
+
+public abstract class BaseLocationObservable implements Observable.OnSubscribeFunc {
+
+ private final Context ctx;
+
+ protected BaseLocationObservable(Context ctx) {
+ this.ctx = ctx;
+ }
+
+ @Override
+ public Subscription onSubscribe(Observer super T> observer) {
+ final LocationConnectionCallbacks locationConnectionCallbacks = new LocationConnectionCallbacks(observer);
+ final LocationClient locationClient = new LocationClient(ctx, locationConnectionCallbacks, locationConnectionCallbacks);
+ locationConnectionCallbacks.setClient(locationClient);
+
+ try {
+ locationClient.connect();
+ } catch (Throwable ex) {
+ observer.onError(ex);
+ }
+
+ return new Subscription() {
+ @Override
+ public void unsubscribe() {
+ if (locationClient.isConnected() || locationClient.isConnecting()) {
+ onUnsubscribed(locationClient);
+ locationClient.disconnect();
+ }
+ }
+ };
+ }
+
+ protected void onUnsubscribed(LocationClient locationClient) {
+ }
+
+ protected abstract void onLocationClientReady(LocationClient locationClient, Observer super T> observer);
+
+ protected abstract void onLocationClientDisconnected(Observer super T> observer);
+
+ private class LocationConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
+ final private Observer super T> observer;
+ private LocationClient locationClient;
+
+ private LocationConnectionCallbacks(Observer super T> observer) {
+ this.observer = observer;
+ }
+
+ @Override
+ public void onConnected(Bundle bundle) {
+ onLocationClientReady(locationClient, observer);
+ }
+
+ @Override
+ public void onDisconnected() {
+ onLocationClientDisconnected(observer);
+ }
+
+ @Override
+ public void onConnectionFailed(ConnectionResult connectionResult) {
+ observer.onError(new LocationConnectionException("Error connecting to LocationClient.", connectionResult));
+ }
+
+ public void setClient(LocationClient client) {
+ this.locationClient = client;
+ }
+ }
+}
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/observables/GeodecodeObservable.java b/library/src/main/java/pl/charmas/android/reactivelocation/observables/GeodecodeObservable.java
new file mode 100644
index 00000000..3422fcd1
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/observables/GeodecodeObservable.java
@@ -0,0 +1,43 @@
+package pl.charmas.android.reactivelocation.observables;
+
+import android.content.Context;
+import android.location.Address;
+import android.location.Geocoder;
+
+import java.io.IOException;
+import java.util.List;
+
+import rx.Observable;
+import rx.Observer;
+import rx.Subscription;
+import rx.subscriptions.Subscriptions;
+
+public class GeodecodeObservable implements Observable.OnSubscribeFunc> {
+ private final Context ctx;
+ private final double latitude;
+ private final double longitude;
+ private final int maxResults;
+
+ public static Observable> createObservable(Context ctx, double latitude, double longitude, int maxResults) {
+ return Observable.create(new GeodecodeObservable(ctx, latitude, longitude, maxResults));
+ }
+
+ private GeodecodeObservable(Context ctx, double latitude, double longitude, int maxResults) {
+ this.ctx = ctx;
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.maxResults = maxResults;
+ }
+
+ @Override
+ public Subscription onSubscribe(Observer super List> observer) {
+ Geocoder geocoder = new Geocoder(ctx);
+ try {
+ observer.onNext(geocoder.getFromLocation(latitude, longitude, maxResults));
+ observer.onCompleted();
+ } catch (IOException e) {
+ observer.onError(e);
+ }
+ return Subscriptions.empty();
+ }
+}
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/observables/LastKnownLocationObservable.java b/library/src/main/java/pl/charmas/android/reactivelocation/observables/LastKnownLocationObservable.java
new file mode 100644
index 00000000..28e240e0
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/observables/LastKnownLocationObservable.java
@@ -0,0 +1,30 @@
+package pl.charmas.android.reactivelocation.observables;
+
+import android.content.Context;
+import android.location.Location;
+
+import com.google.android.gms.location.LocationClient;
+
+import rx.Observable;
+import rx.Observer;
+
+public class LastKnownLocationObservable extends BaseLocationObservable {
+
+ public static Observable createObservable(Context ctx) {
+ return Observable.create(new LastKnownLocationObservable(ctx));
+ }
+
+ private LastKnownLocationObservable(Context ctx) {
+ super(ctx);
+ }
+
+ @Override
+ protected void onLocationClientReady(LocationClient locationClient, Observer super Location> observer) {
+ observer.onNext(locationClient.getLastLocation());
+ observer.onCompleted();
+ }
+
+ @Override
+ protected void onLocationClientDisconnected(Observer super Location> observer) {
+ }
+}
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/observables/LocationUpdatesObservable.java b/library/src/main/java/pl/charmas/android/reactivelocation/observables/LocationUpdatesObservable.java
new file mode 100644
index 00000000..129babda
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/observables/LocationUpdatesObservable.java
@@ -0,0 +1,47 @@
+package pl.charmas.android.reactivelocation.observables;
+
+import android.content.Context;
+import android.location.Location;
+
+import com.google.android.gms.location.LocationClient;
+import com.google.android.gms.location.LocationListener;
+import com.google.android.gms.location.LocationRequest;
+
+import rx.Observable;
+import rx.Observer;
+
+public class LocationUpdatesObservable extends BaseLocationObservable {
+
+ public static Observable createObservable(Context ctx, LocationRequest locationRequest) {
+ return Observable.create(new LocationUpdatesObservable(ctx, locationRequest));
+ }
+
+ private final LocationRequest locationRequest;
+ private LocationListener listener;
+
+ private LocationUpdatesObservable(Context ctx, LocationRequest locationRequest) {
+ super(ctx);
+ this.locationRequest = locationRequest;
+ }
+
+ @Override
+ protected void onLocationClientReady(LocationClient locationClient, final Observer super Location> observer) {
+ listener = new LocationListener() {
+ @Override
+ public void onLocationChanged(Location location) {
+ observer.onNext(location);
+ }
+ };
+ locationClient.requestLocationUpdates(locationRequest, listener);
+ }
+
+ @Override
+ protected void onUnsubscribed(LocationClient locationClient) {
+ locationClient.removeLocationUpdates(listener);
+ }
+
+ @Override
+ protected void onLocationClientDisconnected(Observer super Location> observer) {
+ observer.onCompleted();
+ }
+}
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/AddGeofenceException.java b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/AddGeofenceException.java
new file mode 100644
index 00000000..cd9722d2
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/AddGeofenceException.java
@@ -0,0 +1,14 @@
+package pl.charmas.android.reactivelocation.observables.geofence;
+
+public class AddGeofenceException extends Throwable {
+ private final AddGeofenceObservable.AddGeofenceResult addGeofenceResult;
+
+ public AddGeofenceException(AddGeofenceObservable.AddGeofenceResult addGeofenceResult) {
+ super("Error adding geofences. Status code: " + addGeofenceResult.getStatusCode().getName());
+ this.addGeofenceResult = addGeofenceResult;
+ }
+
+ public AddGeofenceObservable.AddGeofenceResult getAddGeofenceResult() {
+ return addGeofenceResult;
+ }
+}
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/AddGeofenceObservable.java b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/AddGeofenceObservable.java
new file mode 100644
index 00000000..d634b125
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/AddGeofenceObservable.java
@@ -0,0 +1,71 @@
+package pl.charmas.android.reactivelocation.observables.geofence;
+
+import android.app.PendingIntent;
+import android.content.Context;
+
+import com.google.android.gms.location.Geofence;
+import com.google.android.gms.location.LocationClient;
+
+import java.util.List;
+
+import pl.charmas.android.reactivelocation.observables.BaseLocationObservable;
+import rx.Observable;
+import rx.Observer;
+
+public class AddGeofenceObservable extends BaseLocationObservable {
+ private final List gefences;
+ private final PendingIntent geofenceTransitionPendingIntent;
+
+ public static Observable createObservable(Context ctx, List geofences, PendingIntent geofenceTransitionPendingIntent) {
+ return Observable.create(new AddGeofenceObservable(ctx, geofences, geofenceTransitionPendingIntent));
+ }
+
+ private AddGeofenceObservable(Context ctx, List geofences, PendingIntent geofenceTransitionPendingIntent) {
+ super(ctx);
+ this.gefences = geofences;
+ this.geofenceTransitionPendingIntent = geofenceTransitionPendingIntent;
+ }
+
+ @Override
+ protected void onLocationClientReady(LocationClient locationClient, final Observer super AddGeofenceResult> observer) {
+ locationClient.addGeofences(gefences, geofenceTransitionPendingIntent, new LocationClient.OnAddGeofencesResultListener() {
+ @Override
+ public void onAddGeofencesResult(int statusCode, String[] geofenceRequestIds) {
+ AddGeofenceResult result = new AddGeofenceResult(statusCode, geofenceRequestIds);
+ if(LocationStatusCode.ERROR.equals(result.getStatusCode())) {
+ observer.onError(new AddGeofenceException(result));
+ } else {
+ observer.onNext(result);
+ observer.onCompleted();
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void onLocationClientDisconnected(Observer super AddGeofenceResult> observer) {
+ }
+
+ public static final class AddGeofenceResult {
+ private final LocationStatusCode statusCode;
+ private final String[] geofenceRequestIds;
+
+ public AddGeofenceResult(int statusCode, String[] geofenceRequestIds) {
+ this.statusCode = LocationStatusCode.fromCode(statusCode);
+ this.geofenceRequestIds = geofenceRequestIds;
+ }
+
+ public LocationStatusCode getStatusCode() {
+ return statusCode;
+ }
+
+ public String[] getGeofenceRequestIds() {
+ return geofenceRequestIds;
+ }
+
+ public boolean isSuccess() {
+ return statusCode == LocationStatusCode.SUCCESS;
+ }
+ }
+
+}
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/LocationStatusCode.java b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/LocationStatusCode.java
new file mode 100644
index 00000000..bdd546d0
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/LocationStatusCode.java
@@ -0,0 +1,35 @@
+package pl.charmas.android.reactivelocation.observables.geofence;
+
+import com.google.android.gms.location.LocationStatusCodes;
+
+public enum LocationStatusCode {
+ SUCCESS(LocationStatusCodes.SUCCESS, "SUCCESS"),
+ ERROR(LocationStatusCodes.ERROR, "ERROR"),
+ GEOFENCE_NOT_AVAILABLE(LocationStatusCodes.GEOFENCE_NOT_AVAILABLE, "GEOFENCE_NOT_AVAILABLE"),
+ GEOFENCE_TOO_MANY_PENDING_INTENTS(LocationStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS, "GEOFENCE_TOO_MANY_PENDING_INTENTS"),
+ GEOFENCE_TOO_MANY_GEOFENCES(LocationStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS, "GEOFENCE_TOO_MANY_PENDING_INTENTS"),
+ UNKNOWN(-1, "STATUS_CODE_UNKNOWN");
+
+ private final int statusCode;
+ private final String name;
+ LocationStatusCode(int statusCode, String name) {
+ this.statusCode = statusCode;
+ this.name = name;
+ }
+
+ public int getStatusCode() {
+ return statusCode;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public static LocationStatusCode fromCode(int statusCode) {
+ for(LocationStatusCode code: LocationStatusCode.values()) {
+ if(code.statusCode == statusCode)
+ return code;
+ }
+ return UNKNOWN;
+ }
+}
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/PendingIntentRemoveGeofenceObservable.java b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/PendingIntentRemoveGeofenceObservable.java
new file mode 100644
index 00000000..223da1bc
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/PendingIntentRemoveGeofenceObservable.java
@@ -0,0 +1,27 @@
+package pl.charmas.android.reactivelocation.observables.geofence;
+
+import android.app.PendingIntent;
+import android.content.Context;
+
+import com.google.android.gms.location.LocationClient;
+
+import rx.Observer;
+
+class PendingIntentRemoveGeofenceObservable extends RemoveGeofenceObservable {
+ private final PendingIntent pendingIntent;
+
+ PendingIntentRemoveGeofenceObservable(Context ctx, PendingIntent pendingIntent) {
+ super(ctx);
+ this.pendingIntent = pendingIntent;
+ }
+
+ @Override
+ protected void deliverResultToObserver(RemoveGeofencesResult result, Observer super RemoveGeofencesResult.PengingIntentRemoveGeofenceResult> observer) {
+ observer.onNext((RemoveGeofencesResult.PengingIntentRemoveGeofenceResult) result);
+ }
+
+ @Override
+ protected void removeGeofences(LocationClient locationClient, LocationClient.OnRemoveGeofencesResultListener onRemoveGeofencesResultListener) {
+ locationClient.removeGeofences(pendingIntent, onRemoveGeofencesResultListener);
+ }
+}
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/RemoveGeofenceObservable.java b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/RemoveGeofenceObservable.java
new file mode 100644
index 00000000..2ea41053
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/RemoveGeofenceObservable.java
@@ -0,0 +1,61 @@
+package pl.charmas.android.reactivelocation.observables.geofence;
+
+import android.app.PendingIntent;
+import android.content.Context;
+
+import com.google.android.gms.location.LocationClient;
+
+import java.util.List;
+
+import pl.charmas.android.reactivelocation.observables.BaseLocationObservable;
+import rx.Observable;
+import rx.Observer;
+
+public abstract class RemoveGeofenceObservable extends BaseLocationObservable {
+
+ public static Observable createObservable(Context ctx, PendingIntent pendingIntent) {
+ return Observable.create(new PendingIntentRemoveGeofenceObservable(ctx, pendingIntent));
+ }
+
+ public static Observable createObservable(Context ctx, List requestIds) {
+ return Observable.create(new RequestIdsRemoveGeofenceObservable(ctx, requestIds));
+ }
+
+ protected RemoveGeofenceObservable(Context ctx) {
+ super(ctx);
+ }
+
+ @Override
+ protected void onLocationClientReady(LocationClient locationClient, final Observer super T> observer) {
+ removeGeofences(locationClient, new LocationClient.OnRemoveGeofencesResultListener() {
+ @Override
+ public void onRemoveGeofencesByRequestIdsResult(int statusCode, String[] geofenceRequestIds) {
+ publishResult(new RemoveGeofencesResult.RequestIdsRemoveGeofenceResult(statusCode, geofenceRequestIds));
+ }
+
+ @Override
+ public void onRemoveGeofencesByPendingIntentResult(int statusCode, PendingIntent pendingIntent) {
+ publishResult(new RemoveGeofencesResult.PengingIntentRemoveGeofenceResult(statusCode, pendingIntent));
+
+ }
+
+ private void publishResult(RemoveGeofencesResult result) {
+ if (LocationStatusCode.ERROR.equals(result.getStatusCode())) {
+ observer.onError(new RemoveGeofencesException(result.getStatusCode()));
+ } else {
+ deliverResultToObserver(result, observer);
+ observer.onCompleted();
+ }
+ }
+ });
+ }
+
+ protected abstract void deliverResultToObserver(RemoveGeofencesResult result, Observer super T> observer);
+
+ @Override
+ protected void onLocationClientDisconnected(Observer super T> observer) {
+ }
+
+ protected abstract void removeGeofences(LocationClient locationClient, LocationClient.OnRemoveGeofencesResultListener onRemoveGeofencesResultListener);
+
+}
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/RemoveGeofencesException.java b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/RemoveGeofencesException.java
new file mode 100644
index 00000000..850efc5f
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/RemoveGeofencesException.java
@@ -0,0 +1,10 @@
+package pl.charmas.android.reactivelocation.observables.geofence;
+
+public class RemoveGeofencesException extends Throwable {
+ private final LocationStatusCode statusCode;
+
+ public RemoveGeofencesException(LocationStatusCode statusCode) {
+ super("Error removing geofences.");
+ this.statusCode = statusCode;
+ }
+}
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/RemoveGeofencesResult.java b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/RemoveGeofencesResult.java
new file mode 100644
index 00000000..e82bcc80
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/RemoveGeofencesResult.java
@@ -0,0 +1,46 @@
+package pl.charmas.android.reactivelocation.observables.geofence;
+
+import android.app.PendingIntent;
+
+public abstract class RemoveGeofencesResult {
+ private final LocationStatusCode statusCode;
+
+ public RemoveGeofencesResult(int statusCode) {
+ this.statusCode = LocationStatusCode.fromCode(statusCode);
+ }
+
+ public LocationStatusCode getStatusCode() {
+ return statusCode;
+ }
+
+ public boolean isSuccess() {
+ return LocationStatusCode.SUCCESS.equals(statusCode);
+ }
+
+ public static class PengingIntentRemoveGeofenceResult extends RemoveGeofencesResult {
+ private final PendingIntent pendingIntent;
+
+ public PengingIntentRemoveGeofenceResult(int statusCode, PendingIntent pendingIntent) {
+ super(statusCode);
+ this.pendingIntent = pendingIntent;
+ }
+
+ public PendingIntent getPendingIntent() {
+ return pendingIntent;
+ }
+ }
+
+ public static class RequestIdsRemoveGeofenceResult extends RemoveGeofencesResult {
+ private final String[] requestIds;
+
+ public RequestIdsRemoveGeofenceResult(int statusCode, String[] requestIds) {
+ super(statusCode);
+ this.requestIds = requestIds;
+ }
+
+ public String[] getRequestIds() {
+ return requestIds;
+ }
+ }
+
+}
diff --git a/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/RequestIdsRemoveGeofenceObservable.java b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/RequestIdsRemoveGeofenceObservable.java
new file mode 100644
index 00000000..e2750b51
--- /dev/null
+++ b/library/src/main/java/pl/charmas/android/reactivelocation/observables/geofence/RequestIdsRemoveGeofenceObservable.java
@@ -0,0 +1,28 @@
+package pl.charmas.android.reactivelocation.observables.geofence;
+
+import android.content.Context;
+
+import com.google.android.gms.location.LocationClient;
+
+import java.util.List;
+
+import rx.Observer;
+
+class RequestIdsRemoveGeofenceObservable extends RemoveGeofenceObservable {
+ private final List geofenceRequestId;
+
+ RequestIdsRemoveGeofenceObservable(Context ctx, List geofenceRequestId) {
+ super(ctx);
+ this.geofenceRequestId = geofenceRequestId;
+ }
+
+ @Override
+ protected void deliverResultToObserver(RemoveGeofencesResult result, Observer super RemoveGeofencesResult.RequestIdsRemoveGeofenceResult> observer) {
+ observer.onNext((RemoveGeofencesResult.RequestIdsRemoveGeofenceResult) result);
+ }
+
+ @Override
+ protected void removeGeofences(LocationClient locationClient, LocationClient.OnRemoveGeofencesResultListener onRemoveGeofencesResultListener) {
+ locationClient.removeGeofences(geofenceRequestId, onRemoveGeofencesResultListener);
+ }
+}
diff --git a/sample/.gitignore b/sample/.gitignore
new file mode 100644
index 00000000..796b96d1
--- /dev/null
+++ b/sample/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/sample/build.gradle b/sample/build.gradle
new file mode 100644
index 00000000..092d7793
--- /dev/null
+++ b/sample/build.gradle
@@ -0,0 +1,29 @@
+apply plugin: 'android'
+
+android {
+ compileSdkVersion 19
+ buildToolsVersion "19.0.1"
+
+ defaultConfig {
+ minSdkVersion 9
+ targetSdkVersion 19
+ versionCode 1
+ versionName "1.0"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+ buildTypes {
+ release {
+ runProguard false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+}
+
+dependencies {
+ compile 'com.android.support:appcompat-v7:+'
+ compile project(':library')
+}
diff --git a/sample/proguard-rules.txt b/sample/proguard-rules.txt
new file mode 100644
index 00000000..6b133bd3
--- /dev/null
+++ b/sample/proguard-rules.txt
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /home/orbit/utils/android-studio/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# 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 *;
+#}
\ No newline at end of file
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..fa29058a
--- /dev/null
+++ b/sample/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/java/pl/charmas/android/reactivelocation/MainActivity.java b/sample/src/main/java/pl/charmas/android/reactivelocation/MainActivity.java
new file mode 100644
index 00000000..232ba51d
--- /dev/null
+++ b/sample/src/main/java/pl/charmas/android/reactivelocation/MainActivity.java
@@ -0,0 +1,133 @@
+package pl.charmas.android.reactivelocation;
+
+import android.location.Address;
+import android.location.Location;
+import android.support.v7.app.ActionBarActivity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+import com.google.android.gms.location.LocationRequest;
+
+import java.util.List;
+
+import rx.Observable;
+import rx.Subscription;
+import rx.android.observables.AndroidObservable;
+import rx.schedulers.Schedulers;
+import rx.util.functions.Action1;
+import rx.util.functions.Func1;
+
+public class MainActivity extends ActionBarActivity {
+
+ private ReactiveLocationProvider locationProvider;
+
+ private TextView lastKnownLocationView;
+ private TextView updatableLocationView;
+ private TextView addressLocationView;
+
+ private Observable lastKnownLocationObservable;
+ private Observable updatedLocationObservable;
+
+ private Subscription lastKnownLocationSubscription;
+ private Subscription updatableLocationSubscription;
+ private Subscription addressSubscription;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ lastKnownLocationView = (TextView) findViewById(R.id.last_known_location_view);
+ updatableLocationView = (TextView) findViewById(R.id.updated_location_view);
+ addressLocationView = (TextView) findViewById(R.id.address_for_location_view);
+
+ locationProvider = new ReactiveLocationProvider(getApplicationContext());
+ lastKnownLocationObservable = locationProvider.getLastKnownLocation();
+
+ updatedLocationObservable = locationProvider.getUpdatedLocation(
+ LocationRequest.create()
+ .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
+ .setNumUpdates(10)
+ .setInterval(100)
+ );
+
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ lastKnownLocationSubscription = lastKnownLocationObservable
+ .map(new LocationToStringFunc())
+ .subscribe(new DisplayTextOnViewAction(lastKnownLocationView));
+
+ updatableLocationSubscription = updatedLocationObservable
+ .map(new LocationToStringFunc())
+ .map(new Func1() {
+ int count = 0;
+
+ @Override
+ public String call(String s) {
+ return s + " " + count++;
+ }
+ })
+ .subscribe(new DisplayTextOnViewAction(updatableLocationView));
+
+ addressSubscription = AndroidObservable.fromActivity(this, updatedLocationObservable
+ .flatMap(new Func1>>() {
+ @Override
+ public Observable> call(Location location) {
+ return locationProvider.getGeocodeObservable(location.getLatitude(), location.getLongitude(), 1);
+ }
+ })
+ .map(new Func1, Address>() {
+ @Override
+ public Address call(List addresses) {
+ return addresses != null && !addresses.isEmpty() ? addresses.get(0) : null;
+ }
+ })
+ .map(new AddressToStringFunc())
+ .subscribeOn(Schedulers.io()))
+ .subscribe(new DisplayTextOnViewAction(addressLocationView));
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ lastKnownLocationSubscription.unsubscribe();
+ updatableLocationSubscription.unsubscribe();
+ addressSubscription.unsubscribe();
+ }
+
+ private static class AddressToStringFunc implements Func1 {
+ @Override
+ public String call(Address address) {
+ if(address == null) return "";
+
+ String addressLines = "";
+ for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
+ addressLines += address.getAddressLine(i) + '\n';
+ }
+ return addressLines;
+ }
+ }
+
+ private static class LocationToStringFunc implements Func1 {
+ @Override
+ public String call(Location location) {
+ return location.getLatitude() + " " + location.getLongitude() + " (" + location.getAccuracy() + ")";
+ }
+ }
+
+ private static class DisplayTextOnViewAction implements Action1 {
+ private final TextView target;
+
+ private DisplayTextOnViewAction(TextView target) {
+ this.target = target;
+ }
+
+ @Override
+ public void call(String s) {
+ target.setText(s);
+ }
+ }
+}
diff --git a/sample/src/main/res/drawable-hdpi/ic_launcher.png b/sample/src/main/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 00000000..96a442e5
Binary files /dev/null and b/sample/src/main/res/drawable-hdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/drawable-mdpi/ic_launcher.png b/sample/src/main/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 00000000..359047df
Binary files /dev/null and b/sample/src/main/res/drawable-mdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/drawable-xhdpi/ic_launcher.png b/sample/src/main/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..71c6d760
Binary files /dev/null and b/sample/src/main/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/drawable-xxhdpi/ic_launcher.png b/sample/src/main/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..4df18946
Binary files /dev/null and b/sample/src/main/res/drawable-xxhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
new file mode 100644
index 00000000..74db6282
--- /dev/null
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml
new file mode 100644
index 00000000..7b750b45
--- /dev/null
+++ b/sample/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ ReactiveLocation
+ Hello world!
+
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
new file mode 100644
index 00000000..766ab993
--- /dev/null
+++ b/sample/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..52baf7e6
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':sample', ':library'