diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6cbe56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..763cd2f --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +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. \ No newline at end of file diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..aecf397 --- /dev/null +++ b/NOTICE @@ -0,0 +1,46 @@ +RxFit +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e078eae --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Reactive Fit API Library for Android + +This library wraps the Fit API in [RxJava](https://github.com/ReactiveX/RxJava) Observables. No more managing GoogleApiClients! Also, the authorization process for using fitness data is handled by the lib. + +# Usage + +Initialize RxFit once, preferably in your Application `onCreate()` via `RxFit.init(...)`. Make sure to include all the APIs and Scopes that you need for your app. The RxFit class is very similar to the Fitness class provided by the Fit API. Instead of `Fitness.HistoryApi.readData(apiClient, dataReadRequest)` you can use `RxFit.History.read(dataReadRequest)`. Make sure to have the Location and Body Sensors permission from Marshmallow on, if they are needed by your Fit API requests. + +Example: + +```java +RxFit.init( + context, + new Api[] { Fitness.HISTORY_API }, + new Scope[] { new Scope(Scopes.FITNESS_ACTIVITY_READ) } +); + +DataReadRequest dataReadRequest = new DataReadRequest.Builder() + .aggregate(DataType.TYPE_STEP_COUNT_DELTA, DataType.AGGREGATE_STEP_COUNT_DELTA) + .aggregate(DataType.TYPE_CALORIES_EXPENDED, DataType.AGGREGATE_CALORIES_EXPENDED) + .bucketBySession(1, TimeUnit.MINUTES) + .setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS) + .build(); + +RxFit.History.read(dataReadRequest) + .flatMap(dataReadResult -> Observable.from(dataReadResult.getBuckets())) + .subscribe(bucket -> { + /* do something */ + }); +``` + +You can also obtain an `Observable`, which connects on subscribe and disconnects on unsubscribe via `GoogleAPIClientObservable.create(...)`. + +# Sample + +A basic sample app is available in the `sample` project. You need to create an OAuth 2.0 Client ID for the sample app, see the [guide in the Fit API docs](https://developers.google.com/fit/android/get-api-key). + +# Credits + +The code for managing the GoogleApiClient is taken from the [Android-ReactiveLocation](https://github.com/mcharmas/Android-ReactiveLocation) library by Michał Charmas, which is licensed under the Apache License, Version 2.0. + +# 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. \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..16a9eb1 --- /dev/null +++ b/build.gradle @@ -0,0 +1,32 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.0.0-beta6' + + classpath 'me.tatarka:gradle-retrolambda:3.3.0-beta4' + classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2' + + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.2' + classpath "com.github.dcendents:android-maven-gradle-plugin:1.3" + + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } + + configurations.classpath.exclude group: 'com.android.tools.external.lombok' +} + +allprojects { + repositories { + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..1d3591c --- /dev/null +++ b/gradle.properties @@ -0,0 +1,18 @@ +# 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 + +# 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 0000000..13372ae 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 0000000..d0574d2 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Dec 28 10:00:20 PST 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/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 + +# 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 + +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"` + 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=$((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 0000000..aec9973 --- /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 0000000..796b96d --- /dev/null +++ b/library/.gitignore @@ -0,0 +1 @@ +/build diff --git a/library/build.gradle b/library/build.gradle new file mode 100644 index 0000000..c40da24 --- /dev/null +++ b/library/build.gradle @@ -0,0 +1,65 @@ +apply plugin: 'com.android.library' +apply plugin: 'com.jfrog.bintray' +apply plugin: 'com.github.dcendents.android-maven' + +group = 'com.patloew.rxfit' +version = '1.0.0' +project.archivesBaseName = 'rxfit' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + minSdkVersion 9 + targetSdkVersion 23 + versionCode 1 + versionName "1.0.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + testOptions { + unitTests.returnDefaultValues = true + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.android.support:appcompat-v7:23.2.0' + compile 'io.reactivex:rxjava:1.1.1' + compile 'com.google.android.gms:play-services-fitness:8.4.0' + + testCompile 'junit:junit:4.12' + testCompile 'org.mockito:mockito-core:1.10.19' + testCompile "org.powermock:powermock-module-junit4:1.6.4" + testCompile "org.powermock:powermock-module-junit4-rule:1.6.4" + testCompile "org.powermock:powermock-api-mockito:1.6.4" +} + +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 +} + diff --git a/library/proguard-rules.pro b/library/proguard-rules.pro new file mode 100644 index 0000000..1d26e0f --- /dev/null +++ b/library/proguard-rules.pro @@ -0,0 +1,17 @@ +# 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 *; +#} diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e944a2e --- /dev/null +++ b/library/src/main/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/library/src/main/java/com/patloew/rxfit/BaseObservable.java b/library/src/main/java/com/patloew/rxfit/BaseObservable.java new file mode 100644 index 0000000..6209f0d --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/BaseObservable.java @@ -0,0 +1,182 @@ +package com.patloew.rxfit; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +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.ArrayList; +import java.util.List; + +import rx.Observable; +import rx.Observer; +import rx.Subscriber; +import rx.functions.Action0; +import rx.subscriptions.Subscriptions; + +/* 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 abstract class BaseObservable implements Observable.OnSubscribe { + private static final List observableList = new ArrayList<>(); + + private final Context ctx; + private final Api[] services; + private final Scope[] scopes; + private final boolean handleResolution; + private GoogleApiClient apiClient; + Subscriber subscriber; + + protected BaseObservable(@NonNull RxFit rxFit) { + this.ctx = rxFit.getContext(); + this.services = rxFit.getApis(); + this.scopes = rxFit.getScopes(); + handleResolution = true; + } + + protected BaseObservable(@NonNull Context ctx, @NonNull Api[] services, Scope[] scopes) { + this.ctx = ctx; + this.services = services; + this.scopes = scopes; + handleResolution = false; + } + + @Override + public void call(Subscriber subscriber) { + this.subscriber = subscriber; + + apiClient = createApiClient(subscriber); + + try { + apiClient.connect(); + } catch (Throwable ex) { + subscriber.onError(ex); + } + + subscriber.add(Subscriptions.create(new Action0() { + @Override + public void call() { + if (apiClient.isConnected() || apiClient.isConnecting()) { + onUnsubscribed(apiClient); + apiClient.disconnect(); + } + } + })); + } + + + protected GoogleApiClient createApiClient(Subscriber subscriber) { + + ApiClientConnectionCallbacks apiClientConnectionCallbacks = new ApiClientConnectionCallbacks(subscriber); + + GoogleApiClient.Builder apiClientBuilder = new GoogleApiClient.Builder(ctx); + + + 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 locationClient) { } + + protected abstract void onGoogleApiClientReady(GoogleApiClient apiClient, Observer observer); + + private class ApiClientConnectionCallbacks implements + GoogleApiClient.ConnectionCallbacks, + GoogleApiClient.OnConnectionFailedListener { + + final private Observer observer; + + private GoogleApiClient apiClient; + + private ApiClientConnectionCallbacks(Observer observer) { + this.observer = observer; + } + + @Override + public void onConnected(Bundle bundle) { + try { + onGoogleApiClientReady(apiClient, observer); + } catch (Throwable ex) { + observer.onError(ex); + } + } + + @Override + public void onConnectionSuspended(int cause) { + observer.onError(new GoogleAPIConnectionSuspendedException(cause)); + } + + @Override + public void onConnectionFailed(ConnectionResult connectionResult) { + if(handleResolution && connectionResult.hasResolution()) { + observableList.add(BaseObservable.this); + + if(!ResolutionActivity.isResolutionShown()) { + Intent intent = new Intent(ctx, ResolutionActivity.class); + intent.putExtra(ResolutionActivity.ARG_CONNECTION_RESULT, connectionResult); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + ctx.startActivity(intent); + } + } else { + observer.onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient.", connectionResult)); + } + } + + public void setClient(GoogleApiClient client) { + this.apiClient = client; + } + } + + static void onResolutionResult(int resultCode, ConnectionResult connectionResult) { + for(BaseObservable observable : observableList) { + if(!observable.subscriber.isUnsubscribed()) { + if (resultCode == Activity.RESULT_OK && observable.apiClient != null) { + observable.apiClient.connect(); + } else { + observable.subscriber.onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient, resolution was not successful.", connectionResult)); + } + } + } + + observableList.clear(); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/BleClaimDeviceObservable.java b/library/src/main/java/com/patloew/rxfit/BleClaimDeviceObservable.java new file mode 100644 index 0000000..2716acb --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/BleClaimDeviceObservable.java @@ -0,0 +1,43 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.data.BleDevice; + +import rx.Observable; +import rx.Observer; + +public class BleClaimDeviceObservable extends BaseObservable { + + private final BleDevice bleDevice; + private final String deviceAddress; + + static Observable create(@NonNull RxFit rxFit, @NonNull BleDevice bleDevice) { + return Observable.create(new BleClaimDeviceObservable(rxFit, bleDevice, null)); + } + + static Observable create(@NonNull RxFit rxFit, @NonNull String deviceAddress) { + return Observable.create(new BleClaimDeviceObservable(rxFit, null, deviceAddress)); + } + + BleClaimDeviceObservable(RxFit rxFit, BleDevice bleDevice, String deviceAddress) { + super(rxFit); + this.bleDevice = bleDevice; + this.deviceAddress = deviceAddress; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + ResultCallback resultCallback = new StatusResultCallBack(observer); + + if(bleDevice != null) { + Fitness.BleApi.claimBleDevice(apiClient, bleDevice).setResultCallback(resultCallback); + } else { + Fitness.BleApi.claimBleDevice(apiClient, deviceAddress).setResultCallback(resultCallback); + } + } +} diff --git a/library/src/main/java/com/patloew/rxfit/BleListClaimedDevicesObservable.java b/library/src/main/java/com/patloew/rxfit/BleListClaimedDevicesObservable.java new file mode 100644 index 0000000..fd0ab7b --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/BleListClaimedDevicesObservable.java @@ -0,0 +1,53 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.data.BleDevice; +import com.google.android.gms.fitness.data.DataType; +import com.google.android.gms.fitness.result.BleDevicesResult; + +import java.util.List; + +import rx.Observable; +import rx.Observer; + +public class BleListClaimedDevicesObservable extends BaseObservable> { + + private final DataType dataType; + + static Observable> create(@NonNull RxFit rxFit) { + return Observable.create(new BleListClaimedDevicesObservable(rxFit, null)); + } + + static Observable> create(@NonNull RxFit rxFit, DataType dataType) { + return Observable.create(new BleListClaimedDevicesObservable(rxFit, dataType)); + } + + BleListClaimedDevicesObservable(RxFit rxFit, DataType dataType) { + super(rxFit); + this.dataType = dataType; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer> observer) { + Fitness.BleApi.listClaimedBleDevices(apiClient).setResultCallback(new ResultCallback() { + @Override + public void onResult(@NonNull BleDevicesResult bleDevicesResult) { + if (!bleDevicesResult.getStatus().isSuccess()) { + observer.onError(new StatusException(bleDevicesResult.getStatus())); + } else { + if(dataType == null) { + observer.onNext(bleDevicesResult.getClaimedBleDevices()); + } else { + observer.onNext(bleDevicesResult.getClaimedBleDevices(dataType)); + } + + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/BleStartScanObservable.java b/library/src/main/java/com/patloew/rxfit/BleStartScanObservable.java new file mode 100644 index 0000000..1d0045e --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/BleStartScanObservable.java @@ -0,0 +1,44 @@ +package com.patloew.rxfit; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresPermission; +import android.support.v4.content.ContextCompat; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.request.StartBleScanRequest; + +import rx.Observable; +import rx.Observer; + +public class BleStartScanObservable extends BaseObservable { + + private final StartBleScanRequest startBleScanRequest; + + @RequiresPermission("android.permission.BLUETOOTH_ADMIN") + static Observable create(@NonNull RxFit rxFit, @NonNull StartBleScanRequest startBleScanRequest) { + if(ContextCompat.checkSelfPermission(rxFit.getContext(), Manifest.permission.BLUETOOTH_ADMIN) == PackageManager.PERMISSION_GRANTED) { + return Observable.create(new BleStartScanObservable(rxFit, startBleScanRequest)); + } else { + return Observable.error(new PermissionRequiredException(Manifest.permission.BLUETOOTH_ADMIN)); + } + } + + BleStartScanObservable(RxFit rxFit, StartBleScanRequest startBleScanRequest) { + super(rxFit); + this.startBleScanRequest = startBleScanRequest; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + try { + //noinspection MissingPermission + Fitness.BleApi.startBleScan(apiClient, startBleScanRequest).setResultCallback(new StatusResultCallBack(observer)); + } catch(SecurityException e) { + observer.onError(new PermissionRequiredException(Manifest.permission.BLUETOOTH_ADMIN)); + } + } +} diff --git a/library/src/main/java/com/patloew/rxfit/BleStopScanObservable.java b/library/src/main/java/com/patloew/rxfit/BleStopScanObservable.java new file mode 100644 index 0000000..f3e46cb --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/BleStopScanObservable.java @@ -0,0 +1,30 @@ +package com.patloew.rxfit; + +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.fitness.Fitness; +import com.google.android.gms.fitness.request.BleScanCallback; + +import rx.Observable; +import rx.Observer; + +public class BleStopScanObservable extends BaseObservable { + + private final BleScanCallback bleScanCallback; + + static Observable create(@NonNull RxFit rxFit, @NonNull BleScanCallback bleScanCallback) { + return Observable.create(new BleStopScanObservable(rxFit, bleScanCallback)); + } + + BleStopScanObservable(RxFit rxFit, BleScanCallback bleScanCallback) { + super(rxFit); + this.bleScanCallback = bleScanCallback; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.BleApi.stopBleScan(apiClient, bleScanCallback).setResultCallback(new StatusResultCallBack(observer)); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/BleUnclaimDeviceObservable.java b/library/src/main/java/com/patloew/rxfit/BleUnclaimDeviceObservable.java new file mode 100644 index 0000000..23651c4 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/BleUnclaimDeviceObservable.java @@ -0,0 +1,43 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.data.BleDevice; + +import rx.Observable; +import rx.Observer; + +public class BleUnclaimDeviceObservable extends BaseObservable { + + private final BleDevice bleDevice; + private final String deviceAddress; + + static Observable create(@NonNull RxFit rxFit, @NonNull BleDevice bleDevice) { + return Observable.create(new BleUnclaimDeviceObservable(rxFit, bleDevice, null)); + } + + static Observable create(@NonNull RxFit rxFit, @NonNull String deviceAddress) { + return Observable.create(new BleUnclaimDeviceObservable(rxFit, null, deviceAddress)); + } + + BleUnclaimDeviceObservable(RxFit rxFit, BleDevice bleDevice, String deviceAddress) { + super(rxFit); + this.bleDevice = bleDevice; + this.deviceAddress = deviceAddress; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + ResultCallback resultCallback = new StatusResultCallBack(observer); + + if(bleDevice != null) { + Fitness.BleApi.unclaimBleDevice(apiClient, bleDevice).setResultCallback(resultCallback); + } else { + Fitness.BleApi.unclaimBleDevice(apiClient, deviceAddress).setResultCallback(resultCallback); + } + } +} diff --git a/library/src/main/java/com/patloew/rxfit/ConfigCreateCustomDataTypeObservable.java b/library/src/main/java/com/patloew/rxfit/ConfigCreateCustomDataTypeObservable.java new file mode 100644 index 0000000..734b851 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/ConfigCreateCustomDataTypeObservable.java @@ -0,0 +1,42 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.data.DataType; +import com.google.android.gms.fitness.request.DataTypeCreateRequest; +import com.google.android.gms.fitness.result.DataTypeResult; + +import rx.Observable; +import rx.Observer; + +public class ConfigCreateCustomDataTypeObservable extends BaseObservable { + + private final DataTypeCreateRequest dataTypeCreateRequest; + + static Observable create(@NonNull RxFit rxFit, @NonNull DataTypeCreateRequest dataTypeCreateRequest) { + return Observable.create(new ConfigCreateCustomDataTypeObservable(rxFit, dataTypeCreateRequest)); + } + + ConfigCreateCustomDataTypeObservable(RxFit rxFit, DataTypeCreateRequest dataTypeCreateRequest) { + super(rxFit); + this.dataTypeCreateRequest = dataTypeCreateRequest; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.ConfigApi.createCustomDataType(apiClient, dataTypeCreateRequest).setResultCallback(new ResultCallback() { + @Override + public void onResult(@NonNull DataTypeResult dataTypeResult) { + if (!dataTypeResult.getStatus().isSuccess()) { + observer.onError(new StatusException(dataTypeResult.getStatus())); + } else { + observer.onNext(dataTypeResult.getDataType()); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/ConfigDisableFitObservable.java b/library/src/main/java/com/patloew/rxfit/ConfigDisableFitObservable.java new file mode 100644 index 0000000..b10f33f --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/ConfigDisableFitObservable.java @@ -0,0 +1,26 @@ +package com.patloew.rxfit; + +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.fitness.Fitness; + +import rx.Observable; +import rx.Observer; + +public class ConfigDisableFitObservable extends BaseObservable { + + static Observable create(@NonNull RxFit rxFit) { + return Observable.create(new ConfigDisableFitObservable(rxFit)); + } + + ConfigDisableFitObservable(RxFit rxFit) { + super(rxFit); + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.ConfigApi.disableFit(apiClient).setResultCallback(new StatusResultCallBack(observer)); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/ConfigReadDataTypeObservable.java b/library/src/main/java/com/patloew/rxfit/ConfigReadDataTypeObservable.java new file mode 100644 index 0000000..4a97390 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/ConfigReadDataTypeObservable.java @@ -0,0 +1,41 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.data.DataType; +import com.google.android.gms.fitness.result.DataTypeResult; + +import rx.Observable; +import rx.Observer; + +public class ConfigReadDataTypeObservable extends BaseObservable { + + private final String dataTypeName; + + static Observable create(@NonNull RxFit rxFit, @NonNull String dataTypeName) { + return Observable.create(new ConfigReadDataTypeObservable(rxFit, dataTypeName)); + } + + ConfigReadDataTypeObservable(RxFit rxFit, String dataTypeName) { + super(rxFit); + this.dataTypeName = dataTypeName; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.ConfigApi.readDataType(apiClient, dataTypeName).setResultCallback(new ResultCallback() { + @Override + public void onResult(@NonNull DataTypeResult dataTypeResult) { + if (!dataTypeResult.getStatus().isSuccess()) { + observer.onError(new StatusException(dataTypeResult.getStatus())); + } else { + observer.onNext(dataTypeResult.getDataType()); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/GoogleAPIClientObservable.java b/library/src/main/java/com/patloew/rxfit/GoogleAPIClientObservable.java new file mode 100644 index 0000000..fd386a8 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/GoogleAPIClientObservable.java @@ -0,0 +1,52 @@ +package com.patloew.rxfit; + +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 rx.Observable; +import rx.Observer; + +/* 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 GoogleAPIClientObservable extends BaseObservable { + + @SafeVarargs + public static Observable create(@NonNull Context context, @NonNull Api... apis) { + return Observable.create(new GoogleAPIClientObservable(context, apis, null)); + } + + public static Observable create(@NonNull Context context, @NonNull Api[] apis, Scope[] scopes) { + return Observable.create(new GoogleAPIClientObservable(context, apis, scopes)); + } + + GoogleAPIClientObservable(Context ctx, Api[] apis, Scope[] scopes) { + super(ctx, apis, scopes); + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, Observer observer) { + observer.onNext(apiClient); + observer.onCompleted(); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/GoogleAPIConnectionException.java b/library/src/main/java/com/patloew/rxfit/GoogleAPIConnectionException.java new file mode 100644 index 0000000..0830fe0 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/GoogleAPIConnectionException.java @@ -0,0 +1,30 @@ +package com.patloew.rxfit; + +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; + } +} diff --git a/library/src/main/java/com/patloew/rxfit/GoogleAPIConnectionSuspendedException.java b/library/src/main/java/com/patloew/rxfit/GoogleAPIConnectionSuspendedException.java new file mode 100644 index 0000000..281ca37 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/GoogleAPIConnectionSuspendedException.java @@ -0,0 +1,27 @@ +package com.patloew.rxfit; + +/* 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; + } +} diff --git a/library/src/main/java/com/patloew/rxfit/HistoryDeleteDataObservable.java b/library/src/main/java/com/patloew/rxfit/HistoryDeleteDataObservable.java new file mode 100644 index 0000000..9df1b54 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/HistoryDeleteDataObservable.java @@ -0,0 +1,30 @@ +package com.patloew.rxfit; + +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.fitness.Fitness; +import com.google.android.gms.fitness.request.DataDeleteRequest; + +import rx.Observable; +import rx.Observer; + +public class HistoryDeleteDataObservable extends BaseObservable { + + private final DataDeleteRequest dataDeleteRequest; + + static Observable create(@NonNull RxFit rxFit, @NonNull DataDeleteRequest dataDeleteRequest) { + return Observable.create(new HistoryDeleteDataObservable(rxFit, dataDeleteRequest)); + } + + HistoryDeleteDataObservable(RxFit rxFit, DataDeleteRequest dataDeleteRequest) { + super(rxFit); + this.dataDeleteRequest = dataDeleteRequest; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.HistoryApi.deleteData(apiClient, dataDeleteRequest).setResultCallback(new StatusResultCallBack(observer)); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/HistoryInsertDataObservable.java b/library/src/main/java/com/patloew/rxfit/HistoryInsertDataObservable.java new file mode 100644 index 0000000..7f81a7b --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/HistoryInsertDataObservable.java @@ -0,0 +1,30 @@ +package com.patloew.rxfit; + +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.fitness.Fitness; +import com.google.android.gms.fitness.data.DataSet; + +import rx.Observable; +import rx.Observer; + +public class HistoryInsertDataObservable extends BaseObservable { + + private final DataSet dataSet; + + static Observable create(@NonNull RxFit rxFit, @NonNull DataSet dataSet) { + return Observable.create(new HistoryInsertDataObservable(rxFit, dataSet)); + } + + HistoryInsertDataObservable(RxFit rxFit, DataSet dataSet) { + super(rxFit); + this.dataSet = dataSet; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.HistoryApi.insertData(apiClient, dataSet).setResultCallback(new StatusResultCallBack(observer)); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/HistoryReadDailyTotalObservable.java b/library/src/main/java/com/patloew/rxfit/HistoryReadDailyTotalObservable.java new file mode 100644 index 0000000..fdc4b34 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/HistoryReadDailyTotalObservable.java @@ -0,0 +1,42 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.data.DataSet; +import com.google.android.gms.fitness.data.DataType; +import com.google.android.gms.fitness.result.DailyTotalResult; + +import rx.Observable; +import rx.Observer; + +public class HistoryReadDailyTotalObservable extends BaseObservable { + + private final DataType dataType; + + static Observable create(@NonNull RxFit rxFit, @NonNull DataType dataType) { + return Observable.create(new HistoryReadDailyTotalObservable(rxFit, dataType)); + } + + HistoryReadDailyTotalObservable(RxFit rxFit, DataType dataType) { + super(rxFit); + this.dataType = dataType; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.HistoryApi.readDailyTotal(apiClient, dataType).setResultCallback(new ResultCallback() { + @Override + public void onResult(@NonNull DailyTotalResult dailyTotalResult) { + if (!dailyTotalResult.getStatus().isSuccess()) { + observer.onError(new StatusException(dailyTotalResult.getStatus())); + } else { + observer.onNext(dailyTotalResult.getTotal()); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/HistoryReadDataObservable.java b/library/src/main/java/com/patloew/rxfit/HistoryReadDataObservable.java new file mode 100644 index 0000000..68408ec --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/HistoryReadDataObservable.java @@ -0,0 +1,41 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.request.DataReadRequest; +import com.google.android.gms.fitness.result.DataReadResult; + +import rx.Observable; +import rx.Observer; + +public class HistoryReadDataObservable extends BaseObservable { + + private final DataReadRequest dataReadRequest; + + static Observable create(@NonNull RxFit rxFit, @NonNull DataReadRequest dataReadRequest) { + return Observable.create(new HistoryReadDataObservable(rxFit, dataReadRequest)); + } + + HistoryReadDataObservable(RxFit rxFit, DataReadRequest dataReadRequest) { + super(rxFit); + this.dataReadRequest = dataReadRequest; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.HistoryApi.readData(apiClient, dataReadRequest).setResultCallback(new ResultCallback() { + @Override + public void onResult(@NonNull DataReadResult dataReadResult) { + if (!dataReadResult.getStatus().isSuccess()) { + observer.onError(new StatusException(dataReadResult.getStatus())); + } else { + observer.onNext(dataReadResult); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/HistoryUpdateDataObservable.java b/library/src/main/java/com/patloew/rxfit/HistoryUpdateDataObservable.java new file mode 100644 index 0000000..aacbfa1 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/HistoryUpdateDataObservable.java @@ -0,0 +1,30 @@ +package com.patloew.rxfit; + +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.fitness.Fitness; +import com.google.android.gms.fitness.request.DataUpdateRequest; + +import rx.Observable; +import rx.Observer; + +public class HistoryUpdateDataObservable extends BaseObservable { + + private final DataUpdateRequest dataUpdateRequest; + + static Observable create(@NonNull RxFit rxFit, @NonNull DataUpdateRequest dataUpdateRequest) { + return Observable.create(new HistoryUpdateDataObservable(rxFit, dataUpdateRequest)); + } + + HistoryUpdateDataObservable(RxFit rxFit, DataUpdateRequest dataUpdateRequest) { + super(rxFit); + this.dataUpdateRequest = dataUpdateRequest; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.HistoryApi.updateData(apiClient, dataUpdateRequest).setResultCallback(new StatusResultCallBack(observer)); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/PermissionRequiredException.java b/library/src/main/java/com/patloew/rxfit/PermissionRequiredException.java new file mode 100644 index 0000000..af3690b --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/PermissionRequiredException.java @@ -0,0 +1,13 @@ +package com.patloew.rxfit; + +public class PermissionRequiredException extends Throwable { + private final String permission; + + PermissionRequiredException(String permission) { + this.permission = permission; + } + + public String getPermission() { + return permission; + } +} diff --git a/library/src/main/java/com/patloew/rxfit/RecordingListSubscriptionsObservable.java b/library/src/main/java/com/patloew/rxfit/RecordingListSubscriptionsObservable.java new file mode 100644 index 0000000..d558fc1 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/RecordingListSubscriptionsObservable.java @@ -0,0 +1,54 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.data.DataType; +import com.google.android.gms.fitness.data.Subscription; +import com.google.android.gms.fitness.result.ListSubscriptionsResult; + +import java.util.List; + +import rx.Observable; +import rx.Observer; + +public class RecordingListSubscriptionsObservable extends BaseObservable> { + + private final DataType dataType; + + static Observable> create(@NonNull RxFit rxFit) { + return Observable.create(new RecordingListSubscriptionsObservable(rxFit, null)); + } + + static Observable> create(@NonNull RxFit rxFit, DataType dataType) { + return Observable.create(new RecordingListSubscriptionsObservable(rxFit, dataType)); + } + + RecordingListSubscriptionsObservable(RxFit rxFit, DataType dataType) { + super(rxFit); + this.dataType = dataType; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer> observer) { + ResultCallback resultCallback = new ResultCallback() { + @Override + public void onResult(@NonNull ListSubscriptionsResult listSubscriptionsResult) { + if(!listSubscriptionsResult.getStatus().isSuccess()) { + observer.onError(new StatusException(listSubscriptionsResult.getStatus())); + } else { + observer.onNext(listSubscriptionsResult.getSubscriptions()); + observer.onCompleted(); + } + } + }; + + if(dataType == null) { + Fitness.RecordingApi.listSubscriptions(apiClient).setResultCallback(resultCallback); + } else { + Fitness.RecordingApi.listSubscriptions(apiClient, dataType).setResultCallback(resultCallback); + } + } +} diff --git a/library/src/main/java/com/patloew/rxfit/RecordingSubscribeObservable.java b/library/src/main/java/com/patloew/rxfit/RecordingSubscribeObservable.java new file mode 100644 index 0000000..58474d8 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/RecordingSubscribeObservable.java @@ -0,0 +1,44 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.data.DataSource; +import com.google.android.gms.fitness.data.DataType; + +import rx.Observable; +import rx.Observer; + +public class RecordingSubscribeObservable extends BaseObservable { + + private final DataSource dataSource; + private final DataType dataType; + + static Observable create(@NonNull RxFit rxFit, @NonNull DataSource dataSource) { + return Observable.create(new RecordingSubscribeObservable(rxFit, dataSource, null)); + } + + static Observable create(@NonNull RxFit rxFit, @NonNull DataType dataType) { + return Observable.create(new RecordingSubscribeObservable(rxFit, null, dataType)); + } + + RecordingSubscribeObservable(RxFit rxFit, DataSource dataSource, DataType dataType) { + super(rxFit); + this.dataSource = dataSource; + this.dataType = dataType; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + ResultCallback resultCallback = new StatusResultCallBack(observer); + + if(dataSource != null) { + Fitness.RecordingApi.subscribe(apiClient, dataSource).setResultCallback(resultCallback); + } else { + Fitness.RecordingApi.subscribe(apiClient, dataType).setResultCallback(resultCallback); + } + } +} diff --git a/library/src/main/java/com/patloew/rxfit/RecordingUnsubscribeObservable.java b/library/src/main/java/com/patloew/rxfit/RecordingUnsubscribeObservable.java new file mode 100644 index 0000000..71f4dad --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/RecordingUnsubscribeObservable.java @@ -0,0 +1,53 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.data.DataSource; +import com.google.android.gms.fitness.data.DataType; +import com.google.android.gms.fitness.data.Subscription; + +import rx.Observable; +import rx.Observer; + +public class RecordingUnsubscribeObservable extends BaseObservable { + + private final DataSource dataSource; + private final DataType dataType; + private final Subscription subscription; + + static Observable create(@NonNull RxFit rxFit, @NonNull DataSource dataSource) { + return Observable.create(new RecordingUnsubscribeObservable(rxFit, dataSource, null, null)); + } + + static Observable create(@NonNull RxFit rxFit, @NonNull DataType dataType) { + return Observable.create(new RecordingUnsubscribeObservable(rxFit, null, dataType, null)); + } + + static Observable create(@NonNull RxFit rxFit, @NonNull Subscription subscription) { + return Observable.create(new RecordingUnsubscribeObservable(rxFit, null, null, subscription)); + } + + RecordingUnsubscribeObservable(RxFit rxFit, DataSource dataSource, DataType dataType, Subscription subscription) { + super(rxFit); + this.dataSource = dataSource; + this.dataType = dataType; + this.subscription = subscription; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + ResultCallback resultCallback = new StatusResultCallBack(observer); + + if(dataSource != null) { + Fitness.RecordingApi.unsubscribe(apiClient, dataSource).setResultCallback(resultCallback); + } else if(dataType != null) { + Fitness.RecordingApi.unsubscribe(apiClient, dataType).setResultCallback(resultCallback); + } else { + Fitness.RecordingApi.unsubscribe(apiClient, subscription).setResultCallback(resultCallback); + } + } +} diff --git a/library/src/main/java/com/patloew/rxfit/ResolutionActivity.java b/library/src/main/java/com/patloew/rxfit/ResolutionActivity.java new file mode 100644 index 0000000..99cf6ff --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/ResolutionActivity.java @@ -0,0 +1,61 @@ +package com.patloew.rxfit; + +import android.app.Activity; +import android.content.Intent; +import android.content.IntentSender; +import android.os.Bundle; + +import com.google.android.gms.common.ConnectionResult; + +public class ResolutionActivity extends Activity { + + protected static final String ARG_CONNECTION_RESULT = "connectionResult"; + + private static final int REQUEST_CODE_RESOLUTION = 123; + + private static boolean resolutionShown = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if(savedInstanceState == null) { + handleIntent(); + } + } + + @Override + protected void onNewIntent(Intent intent) { + setIntent(intent); + handleIntent(); + } + + private void handleIntent() { + try { + ConnectionResult connectionResult = getIntent().getParcelableExtra(ARG_CONNECTION_RESULT); + connectionResult.startResolutionForResult(this, REQUEST_CODE_RESOLUTION); + resolutionShown = true; + } 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); + } + } + + private void setResolutionResultAndFinish(int resultCode) { + resolutionShown = false; + BaseObservable.onResolutionResult(resultCode, (ConnectionResult) getIntent().getParcelableExtra(ARG_CONNECTION_RESULT)); + finish(); + } + + static boolean isResolutionShown() { + return resolutionShown; + } +} diff --git a/library/src/main/java/com/patloew/rxfit/RxFit.java b/library/src/main/java/com/patloew/rxfit/RxFit.java new file mode 100644 index 0000000..3ccf251 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/RxFit.java @@ -0,0 +1,264 @@ +package com.patloew.rxfit; + +import android.app.PendingIntent; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresPermission; + +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.fitness.data.BleDevice; +import com.google.android.gms.fitness.data.DataPoint; +import com.google.android.gms.fitness.data.DataSet; +import com.google.android.gms.fitness.data.DataSource; +import com.google.android.gms.fitness.data.DataType; +import com.google.android.gms.fitness.data.Session; +import com.google.android.gms.fitness.data.Subscription; +import com.google.android.gms.fitness.request.BleScanCallback; +import com.google.android.gms.fitness.request.DataDeleteRequest; +import com.google.android.gms.fitness.request.DataReadRequest; +import com.google.android.gms.fitness.request.DataSourcesRequest; +import com.google.android.gms.fitness.request.DataTypeCreateRequest; +import com.google.android.gms.fitness.request.DataUpdateRequest; +import com.google.android.gms.fitness.request.SensorRequest; +import com.google.android.gms.fitness.request.SessionInsertRequest; +import com.google.android.gms.fitness.request.SessionReadRequest; +import com.google.android.gms.fitness.request.StartBleScanRequest; +import com.google.android.gms.fitness.result.DataReadResult; +import com.google.android.gms.fitness.result.SessionReadResult; + +import java.util.List; + +import rx.Observable; + +/* Factory for Google Fit API observables. Make sure to include all the APIs + * and Scopes that you need for your app. Also make sure to have the Location + * and Body Sensors permission on Marshmallow, if they are needed by your + * Fit API requests. + */ +public class RxFit { + + private static RxFit instance = null; + + private final Context ctx; + private final Api[] apis; + private final Scope[] scopes; + + /* Initializes the singleton instance of RxFitProvider + * + * @param ctx Context. + * @param apis An array of Fitness APIs to be used in your app. + * @param scopes An array of the Scopes to be requested for your app. + */ + public static void init(@NonNull Context ctx, @NonNull Api[] apis, @NonNull Scope[] scopes) { + if(instance == null) { instance = new RxFit(ctx, apis, scopes); } + } + + /* Gets the singleton instance of RxFitProvider, after it was + * initialized. + */ + private static RxFit get() { + if(instance == null) { throw new IllegalStateException("RxFitProvider not initialized"); } + return instance; + } + + + private RxFit(@NonNull Context ctx, @NonNull Api[] apis, @NonNull Scope[] scopes) { + this.ctx = ctx.getApplicationContext(); + this.apis = apis; + this.scopes = scopes; + } + + Context getContext() { + return ctx; + } + + Api[] getApis() { + return apis; + } + + Scope[] getScopes() { + return scopes; + } + + + + public static class Ble { + + private Ble() { } + + public static Observable claimDevice(@NonNull BleDevice bleDevice) { + return BleClaimDeviceObservable.create(RxFit.get(), bleDevice); + } + + public static Observable claimDevice(@NonNull String deviceAddress) { + return BleClaimDeviceObservable.create(RxFit.get(), deviceAddress); + } + + public static Observable> getClaimedDeviceList() { + return BleListClaimedDevicesObservable.create(RxFit.get()); + } + + public static Observable> getClaimedDeviceList(DataType dataType) { + return BleListClaimedDevicesObservable.create(RxFit.get(), dataType); + } + + @RequiresPermission("android.permission.BLUETOOTH_ADMIN") + public static Observable startScan(@NonNull StartBleScanRequest startBleScanRequest) { + return BleStartScanObservable.create(RxFit.get(), startBleScanRequest); + } + + public static Observable stopScan(@NonNull BleScanCallback bleScanCallback) { + return BleStopScanObservable.create(RxFit.get(), bleScanCallback); + } + + public static Observable unclaimDevice(@NonNull BleDevice bleDevice) { + return BleUnclaimDeviceObservable.create(RxFit.get(), bleDevice); + } + + public static Observable unclaimDevice(@NonNull String deviceAddress) { + return BleUnclaimDeviceObservable.create(RxFit.get(), deviceAddress); + } + + } + + + public static class Config { + + private Config() { } + + public static Observable createCustomDataType(@NonNull DataTypeCreateRequest dataTypeCreateRequest) { + return ConfigCreateCustomDataTypeObservable.create(RxFit.get(), dataTypeCreateRequest); + } + + public static Observable disableFit() { + return ConfigDisableFitObservable.create(RxFit.get()); + } + + public static Observable readDataType(@NonNull String dataTypeName) { + return ConfigReadDataTypeObservable.create(RxFit.get(), dataTypeName); + } + + } + + + public static class History { + + private History() { } + + public static Observable delete(@NonNull DataDeleteRequest dataDeleteRequest) { + return HistoryDeleteDataObservable.create(RxFit.get(), dataDeleteRequest); + } + + public static Observable insert(@NonNull DataSet dataSet) { + return HistoryInsertDataObservable.create(RxFit.get(), dataSet); + } + + public static Observable readDailyTotal(@NonNull DataType dataType) { + return HistoryReadDailyTotalObservable.create(RxFit.get(), dataType); + } + + public static Observable read(@NonNull DataReadRequest dataReadRequest) { + return HistoryReadDataObservable.create(RxFit.get(), dataReadRequest); + } + + public static Observable update(@NonNull DataUpdateRequest dataUpdateRequest) { + return HistoryUpdateDataObservable.create(RxFit.get(), dataUpdateRequest); + } + + } + + + public static class Recording { + + private Recording() { } + + public static Observable> listSubscriptions() { + return RecordingListSubscriptionsObservable.create(RxFit.get()); + } + + public static Observable> listSubscriptions(DataType dataType) { + return RecordingListSubscriptionsObservable.create(RxFit.get(), dataType); + } + + public static Observable subscribe(@NonNull DataSource dataSource) { + return RecordingSubscribeObservable.create(RxFit.get(), dataSource); + } + + public static Observable subscribe(@NonNull DataType dataType) { + return RecordingSubscribeObservable.create(RxFit.get(), dataType); + } + + public static Observable unsubscribe(@NonNull DataSource dataSource) { + return RecordingUnsubscribeObservable.create(RxFit.get(), dataSource); + } + + public static Observable unsubscribe(@NonNull DataType dataType) { + return RecordingUnsubscribeObservable.create(RxFit.get(), dataType); + } + + public static Observable unsubscribe(@NonNull Subscription subscription) { + return RecordingUnsubscribeObservable.create(RxFit.get(), subscription); + } + + } + + + public static class Sensors { + + private Sensors() { } + + public static Observable addDataPointIntent(@NonNull SensorRequest sensorRequest, @NonNull PendingIntent pendingIntent) { + return SensorsAddDataPointIntentObservable.create(RxFit.get(), sensorRequest, pendingIntent); + } + + public static Observable removeDataPointIntent(@NonNull PendingIntent pendingIntent) { + return SensorsRemoveDataPointIntentObservable.create(RxFit.get(), pendingIntent); + } + + public static Observable getDataPoints(@NonNull SensorRequest sensorRequest) { + return SensorsDataPointObservable.create(RxFit.get(), sensorRequest); + } + + public static Observable> findDataSources(@NonNull DataSourcesRequest dataSourcesRequest) { + return SensorsFindDataSourcesObservable.create(RxFit.get(), dataSourcesRequest); + } + + public static Observable> findDataSources(@NonNull DataSourcesRequest dataSourcesRequest, DataType dataType) { + return SensorsFindDataSourcesObservable.create(RxFit.get(), dataSourcesRequest, dataType); + } + + } + + + public static class Sessions { + + private Sessions() { } + + public static Observable insert(@NonNull SessionInsertRequest sessionInsertRequest) { + return SessionInsertObservable.create(RxFit.get(), sessionInsertRequest); + } + + public static Observable read(@NonNull SessionReadRequest sessionReadRequest) { + return SessionReadObservable.create(RxFit.get(), sessionReadRequest); + } + + public static Observable registerForSessions(@NonNull PendingIntent pendingIntent) { + return SessionRegisterObservable.create(RxFit.get(), pendingIntent); + } + + public static Observable unregisterForSessions(@NonNull PendingIntent pendingIntent) { + return SessionUnregisterObservable.create(RxFit.get(), pendingIntent); + } + + public static Observable start(@NonNull Session session) { + return SessionStartObservable.create(RxFit.get(), session); + } + + public static Observable> stop(@NonNull String identifier) { + return SessionStopObservable.create(RxFit.get(), identifier); + } + } + +} diff --git a/library/src/main/java/com/patloew/rxfit/SensorsAddDataPointIntentObservable.java b/library/src/main/java/com/patloew/rxfit/SensorsAddDataPointIntentObservable.java new file mode 100644 index 0000000..e7046b8 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/SensorsAddDataPointIntentObservable.java @@ -0,0 +1,33 @@ +package com.patloew.rxfit; + +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.fitness.Fitness; +import com.google.android.gms.fitness.request.SensorRequest; + +import rx.Observable; +import rx.Observer; + +public class SensorsAddDataPointIntentObservable extends BaseObservable { + + private final SensorRequest sensorRequest; + private final PendingIntent pendingIntent; + + static Observable create(@NonNull RxFit rxFit, @NonNull SensorRequest sensorRequest, @NonNull PendingIntent pendingIntent) { + return Observable.create(new SensorsAddDataPointIntentObservable(rxFit, sensorRequest, pendingIntent)); + } + + SensorsAddDataPointIntentObservable(RxFit rxFit, SensorRequest sensorRequest, PendingIntent pendingIntent) { + super(rxFit); + this.sensorRequest = sensorRequest; + this.pendingIntent = pendingIntent; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.SensorsApi.add(apiClient, sensorRequest, pendingIntent).setResultCallback(new StatusResultCallBack(observer)); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/SensorsDataPointObservable.java b/library/src/main/java/com/patloew/rxfit/SensorsDataPointObservable.java new file mode 100644 index 0000000..719d7b7 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/SensorsDataPointObservable.java @@ -0,0 +1,53 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.data.DataPoint; +import com.google.android.gms.fitness.request.OnDataPointListener; +import com.google.android.gms.fitness.request.SensorRequest; + +import rx.Observable; +import rx.Observer; + +public class SensorsDataPointObservable extends BaseObservable { + + private final SensorRequest sensorRequest; + private OnDataPointListener dataPointListener = null; + + static Observable create(@NonNull RxFit rxFit, @NonNull SensorRequest sensorRequest) { + return Observable.create(new SensorsDataPointObservable(rxFit, sensorRequest)); + } + + SensorsDataPointObservable(RxFit rxFit, SensorRequest sensorRequest) { + super(rxFit); + this.sensorRequest = sensorRequest; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + dataPointListener = new OnDataPointListener() { + @Override + public void onDataPoint(DataPoint dataPoint) { + observer.onNext(dataPoint); + } + }; + + Fitness.SensorsApi.add(apiClient, sensorRequest, dataPointListener).setResultCallback(new ResultCallback() { + @Override + public void onResult(@NonNull Status status) { + if (!status.isSuccess()) { + observer.onError(new StatusException(status)); + } + } + }); + } + + @Override + protected void onUnsubscribed(GoogleApiClient apiClient) { + Fitness.SensorsApi.remove(apiClient, dataPointListener); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/SensorsFindDataSourcesObservable.java b/library/src/main/java/com/patloew/rxfit/SensorsFindDataSourcesObservable.java new file mode 100644 index 0000000..84f2bd9 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/SensorsFindDataSourcesObservable.java @@ -0,0 +1,56 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.data.DataSource; +import com.google.android.gms.fitness.data.DataType; +import com.google.android.gms.fitness.request.DataSourcesRequest; +import com.google.android.gms.fitness.result.DataSourcesResult; + +import java.util.List; + +import rx.Observable; +import rx.Observer; + +public class SensorsFindDataSourcesObservable extends BaseObservable> { + + private final DataSourcesRequest dataSourcesRequest; + private final DataType dataType; + + static Observable> create(@NonNull RxFit rxFit, @NonNull DataSourcesRequest dataSourcesRequest) { + return Observable.create(new SensorsFindDataSourcesObservable(rxFit, dataSourcesRequest, null)); + } + + static Observable> create(@NonNull RxFit rxFit, @NonNull DataSourcesRequest dataSourcesRequest, DataType dataType) { + return Observable.create(new SensorsFindDataSourcesObservable(rxFit, dataSourcesRequest, dataType)); + } + + SensorsFindDataSourcesObservable(RxFit rxFit, DataSourcesRequest dataSourcesRequest, DataType dataType) { + super(rxFit); + this.dataSourcesRequest = dataSourcesRequest; + this.dataType = dataType; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer> observer) { + Fitness.SensorsApi.findDataSources(apiClient, dataSourcesRequest).setResultCallback(new ResultCallback() { + @Override + public void onResult(@NonNull DataSourcesResult dataSourcesResult) { + if (!dataSourcesResult.getStatus().isSuccess()) { + observer.onError(new StatusException(dataSourcesResult.getStatus())); + } else { + if(dataType == null) { + observer.onNext(dataSourcesResult.getDataSources()); + } else { + observer.onNext(dataSourcesResult.getDataSources(dataType)); + } + + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/SensorsRemoveDataPointIntentObservable.java b/library/src/main/java/com/patloew/rxfit/SensorsRemoveDataPointIntentObservable.java new file mode 100644 index 0000000..b9e729d --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/SensorsRemoveDataPointIntentObservable.java @@ -0,0 +1,30 @@ +package com.patloew.rxfit; + +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.fitness.Fitness; + +import rx.Observable; +import rx.Observer; + +public class SensorsRemoveDataPointIntentObservable extends BaseObservable { + + private final PendingIntent pendingIntent; + + static Observable create(@NonNull RxFit rxFit, @NonNull PendingIntent pendingIntent) { + return Observable.create(new SensorsRemoveDataPointIntentObservable(rxFit, pendingIntent)); + } + + SensorsRemoveDataPointIntentObservable(RxFit rxFit, PendingIntent pendingIntent) { + super(rxFit); + this.pendingIntent = pendingIntent; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.SensorsApi.remove(apiClient, pendingIntent).setResultCallback(new StatusResultCallBack(observer)); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/SessionInsertObservable.java b/library/src/main/java/com/patloew/rxfit/SessionInsertObservable.java new file mode 100644 index 0000000..28f2e7a --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/SessionInsertObservable.java @@ -0,0 +1,30 @@ +package com.patloew.rxfit; + +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.fitness.Fitness; +import com.google.android.gms.fitness.request.SessionInsertRequest; + +import rx.Observable; +import rx.Observer; + +public class SessionInsertObservable extends BaseObservable { + + private final SessionInsertRequest sessionInsertRequest; + + static Observable create(@NonNull RxFit rxFit, @NonNull SessionInsertRequest sessionInsertRequest) { + return Observable.create(new SessionInsertObservable(rxFit, sessionInsertRequest)); + } + + SessionInsertObservable(RxFit rxFit, SessionInsertRequest sessionInsertRequest) { + super(rxFit); + this.sessionInsertRequest = sessionInsertRequest; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.SessionsApi.insertSession(apiClient, sessionInsertRequest).setResultCallback(new StatusResultCallBack(observer)); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/SessionReadObservable.java b/library/src/main/java/com/patloew/rxfit/SessionReadObservable.java new file mode 100644 index 0000000..94ab6a9 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/SessionReadObservable.java @@ -0,0 +1,41 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.request.SessionReadRequest; +import com.google.android.gms.fitness.result.SessionReadResult; + +import rx.Observable; +import rx.Observer; + +public class SessionReadObservable extends BaseObservable { + + private final SessionReadRequest sessionReadRequest; + + static Observable create(@NonNull RxFit rxFit, @NonNull SessionReadRequest sessionReadRequest) { + return Observable.create(new SessionReadObservable(rxFit, sessionReadRequest)); + } + + SessionReadObservable(RxFit rxFit, SessionReadRequest sessionReadRequest) { + super(rxFit); + this.sessionReadRequest = sessionReadRequest; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.SessionsApi.readSession(apiClient, sessionReadRequest).setResultCallback(new ResultCallback() { + @Override + public void onResult(@NonNull SessionReadResult sessionReadResult) { + if (!sessionReadResult.getStatus().isSuccess()) { + observer.onError(new StatusException(sessionReadResult.getStatus())); + } else { + observer.onNext(sessionReadResult); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/SessionRegisterObservable.java b/library/src/main/java/com/patloew/rxfit/SessionRegisterObservable.java new file mode 100644 index 0000000..fadd72b --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/SessionRegisterObservable.java @@ -0,0 +1,30 @@ +package com.patloew.rxfit; + +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.fitness.Fitness; + +import rx.Observable; +import rx.Observer; + +public class SessionRegisterObservable extends BaseObservable { + + private final PendingIntent pendingIntent; + + static Observable create(@NonNull RxFit rxFit, @NonNull PendingIntent pendingIntent) { + return Observable.create(new SessionRegisterObservable(rxFit, pendingIntent)); + } + + SessionRegisterObservable(RxFit rxFit, PendingIntent pendingIntent) { + super(rxFit); + this.pendingIntent = pendingIntent; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.SessionsApi.registerForSessions(apiClient, pendingIntent).setResultCallback(new StatusResultCallBack(observer)); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/SessionStartObservable.java b/library/src/main/java/com/patloew/rxfit/SessionStartObservable.java new file mode 100644 index 0000000..6d0a01e --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/SessionStartObservable.java @@ -0,0 +1,30 @@ +package com.patloew.rxfit; + +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.fitness.Fitness; +import com.google.android.gms.fitness.data.Session; + +import rx.Observable; +import rx.Observer; + +public class SessionStartObservable extends BaseObservable { + + private final Session session; + + static Observable create(@NonNull RxFit rxFit, @NonNull Session session) { + return Observable.create(new SessionStartObservable(rxFit, session)); + } + + SessionStartObservable(RxFit rxFit, Session session) { + super(rxFit); + this.session = session; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.SessionsApi.startSession(apiClient, session).setResultCallback(new StatusResultCallBack(observer)); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/SessionStopObservable.java b/library/src/main/java/com/patloew/rxfit/SessionStopObservable.java new file mode 100644 index 0000000..57c27cd --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/SessionStopObservable.java @@ -0,0 +1,43 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.data.Session; +import com.google.android.gms.fitness.result.SessionStopResult; + +import java.util.List; + +import rx.Observable; +import rx.Observer; + +public class SessionStopObservable extends BaseObservable> { + + private final String identifier; + + static Observable> create(@NonNull RxFit rxFit, @NonNull String identifier) { + return Observable.create(new SessionStopObservable(rxFit, identifier)); + } + + SessionStopObservable(RxFit rxFit, String identifier) { + super(rxFit); + this.identifier = identifier; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer> observer) { + Fitness.SessionsApi.stopSession(apiClient, identifier).setResultCallback(new ResultCallback() { + @Override + public void onResult(@NonNull SessionStopResult sessionStopResult) { + if (!sessionStopResult.getStatus().isSuccess()) { + observer.onError(new StatusException(sessionStopResult.getStatus())); + } else { + observer.onNext(sessionStopResult.getSessions()); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/SessionUnregisterObservable.java b/library/src/main/java/com/patloew/rxfit/SessionUnregisterObservable.java new file mode 100644 index 0000000..c9f331f --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/SessionUnregisterObservable.java @@ -0,0 +1,30 @@ +package com.patloew.rxfit; + +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.fitness.Fitness; + +import rx.Observable; +import rx.Observer; + +public class SessionUnregisterObservable extends BaseObservable { + + private final PendingIntent pendingIntent; + + static Observable create(@NonNull RxFit rxFit, @NonNull PendingIntent pendingIntent) { + return Observable.create(new SessionUnregisterObservable(rxFit, pendingIntent)); + } + + SessionUnregisterObservable(RxFit rxFit, PendingIntent pendingIntent) { + super(rxFit); + this.pendingIntent = pendingIntent; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + Fitness.SessionsApi.unregisterForSessions(apiClient, pendingIntent).setResultCallback(new StatusResultCallBack(observer)); + } +} diff --git a/library/src/main/java/com/patloew/rxfit/StatusException.java b/library/src/main/java/com/patloew/rxfit/StatusException.java new file mode 100644 index 0000000..fb11edd --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/StatusException.java @@ -0,0 +1,29 @@ +package com.patloew.rxfit; + +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. + */ +public class StatusException extends Throwable { + private final Status status; + + public StatusException(Status status) { + this.status = status; + } + + public Status getStatus() { + return status; + } +} diff --git a/library/src/main/java/com/patloew/rxfit/StatusResultCallBack.java b/library/src/main/java/com/patloew/rxfit/StatusResultCallBack.java new file mode 100644 index 0000000..4cf7c48 --- /dev/null +++ b/library/src/main/java/com/patloew/rxfit/StatusResultCallBack.java @@ -0,0 +1,27 @@ +package com.patloew.rxfit; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.api.Status; + +import rx.Observer; + +public class StatusResultCallBack implements ResultCallback { + + private final Observer observer; + + public StatusResultCallBack(@NonNull Observer observer) { + this.observer = observer; + } + + @Override + public void onResult(@NonNull Status status) { + if (!status.isSuccess()) { + observer.onError(new StatusException(status)); + } else { + observer.onNext(status); + observer.onCompleted(); + } + } +} diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml new file mode 100644 index 0000000..851a9a9 --- /dev/null +++ b/library/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + RxFit + diff --git a/library/src/test/java/com/patloew/rxfit/RxFitTest.java b/library/src/test/java/com/patloew/rxfit/RxFitTest.java new file mode 100644 index 0000000..3de76e8 --- /dev/null +++ b/library/src/test/java/com/patloew/rxfit/RxFitTest.java @@ -0,0 +1,1511 @@ +package com.patloew.rxfit; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.pm.PackageManager; +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.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.common.api.Status; +import com.google.android.gms.fitness.BleApi; +import com.google.android.gms.fitness.ConfigApi; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.HistoryApi; +import com.google.android.gms.fitness.RecordingApi; +import com.google.android.gms.fitness.SensorsApi; +import com.google.android.gms.fitness.SessionsApi; +import com.google.android.gms.fitness.data.BleDevice; +import com.google.android.gms.fitness.data.DataPoint; +import com.google.android.gms.fitness.data.DataSet; +import com.google.android.gms.fitness.data.DataSource; +import com.google.android.gms.fitness.data.DataType; +import com.google.android.gms.fitness.data.Session; +import com.google.android.gms.fitness.data.Subscription; +import com.google.android.gms.fitness.request.BleScanCallback; +import com.google.android.gms.fitness.request.DataDeleteRequest; +import com.google.android.gms.fitness.request.DataReadRequest; +import com.google.android.gms.fitness.request.DataSourcesRequest; +import com.google.android.gms.fitness.request.DataTypeCreateRequest; +import com.google.android.gms.fitness.request.DataUpdateRequest; +import com.google.android.gms.fitness.request.OnDataPointListener; +import com.google.android.gms.fitness.request.SensorRequest; +import com.google.android.gms.fitness.request.SessionInsertRequest; +import com.google.android.gms.fitness.request.SessionReadRequest; +import com.google.android.gms.fitness.request.StartBleScanRequest; +import com.google.android.gms.fitness.result.BleDevicesResult; +import com.google.android.gms.fitness.result.DailyTotalResult; +import com.google.android.gms.fitness.result.DataReadResult; +import com.google.android.gms.fitness.result.DataSourcesResult; +import com.google.android.gms.fitness.result.DataTypeResult; +import com.google.android.gms.fitness.result.ListSubscriptionsResult; +import com.google.android.gms.fitness.result.SessionReadResult; +import com.google.android.gms.fitness.result.SessionStopResult; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareOnlyThisForTest; +import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +import java.util.ArrayList; +import java.util.List; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(PowerMockRunner.class) +@PrepareOnlyThisForTest({ ContextCompat.class, Fitness.class, Status.class, ConnectionResult.class, DataType.class, DataSet.class, DataPoint.class }) +@SuppressStaticInitializationFor("com.google.android.gms.fitness.Fitness") +public class RxFitTest { + + @Mock Context ctx; + + @Mock GoogleApiClient apiClient; + @Mock Status status; + @Mock ConnectionResult connectionResult; + @Mock PendingResult pendingResult; + + @Mock DataType dataType; + @Mock DataSource dataSource; + @Mock DataSet dataSet; + @Mock BleDevice bleDevice; + @Mock Subscription subscription; + @Mock SensorRequest sensorRequest; + @Mock Session session; + + @Mock BleApi bleApi; + @Mock ConfigApi configApi; + @Mock HistoryApi historyApi; + @Mock RecordingApi recordingApi; + @Mock SensorsApi sensorsApi; + @Mock SessionsApi sessionsApi; + + @Mock RxFit rxFit; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + PowerMockito.mockStatic(Fitness.class); + Whitebox.setInternalState(Fitness.class, bleApi); + Whitebox.setInternalState(Fitness.class, configApi); + Whitebox.setInternalState(Fitness.class, historyApi); + Whitebox.setInternalState(Fitness.class, recordingApi); + Whitebox.setInternalState(Fitness.class, sensorsApi); + Whitebox.setInternalState(Fitness.class, sessionsApi); + + when(ctx.getApplicationContext()).thenReturn(ctx); + } + + ////////////////// + // UTIL METHODS // + ////////////////// + + @SuppressWarnings("unchecked") + // Mock GoogleApiClient connection success behaviour + private void setupBaseObservableSuccess(final BaseObservable baseObservable) { + doReturn(apiClient).when(baseObservable).createApiClient(Matchers.any()); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + baseObservable.onGoogleApiClientReady(apiClient, baseObservable.subscriber); + return null; + } + }).when(apiClient).connect(); + } + + @SuppressWarnings("unchecked") + private void setPendingResultValue(final Result result) { + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + ((ResultCallback)invocation.getArguments()[0]).onResult(result); + return null; + } + }).when(pendingResult).setResultCallback(Matchers.any()); + } + + private void assertError(TestSubscriber sub, Class errorClass) { + sub.assertError(errorClass); + sub.assertNoValues(); + sub.assertTerminalEvent(); + sub.assertUnsubscribed(); + } + + @SuppressWarnings("unchecked") + private void assertSingleValue(TestSubscriber sub, Object value) { + sub.assertNoErrors(); + sub.assertTerminalEvent(); + sub.assertUnsubscribed(); + sub.assertValue(value); + } + + + ////////////////////// + // OBSERVABLE TESTS // + ////////////////////// + + + // GoogleApiClientObservable + + @Test + public void GoogleAPIClientObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + GoogleAPIClientObservable observable = spy(new GoogleAPIClientObservable(ctx, new Api[] {}, new Scope[] {})); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, apiClient); + } + + @Test + public void GoogleAPIClientObservable_ConnectionException() { + TestSubscriber sub = new TestSubscriber<>(); + final GoogleAPIClientObservable observable = spy(new GoogleAPIClientObservable(ctx, new Api[] {}, new Scope[] {})); + + // Mock GoogleApiClient connection error behaviour + doReturn(apiClient).when(observable).createApiClient(Matchers.any()); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + observable.subscriber.onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient.", connectionResult)); + return null; + } + }).when(apiClient).connect(); + + Observable.create(observable).subscribe(sub); + + assertError(sub, GoogleAPIConnectionException.class); + } + + + /******* + * BLE * + *******/ + + // BleClaimDeviceObservable + + @Test + public void BleClaimDeviceObservable_BleDevice_Success() { + TestSubscriber sub = new TestSubscriber<>(); + BleDevice bleDevice = Mockito.mock(BleDevice.class); + BleClaimDeviceObservable observable = spy(new BleClaimDeviceObservable(rxFit, bleDevice, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(bleApi.claimBleDevice(apiClient, bleDevice)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void BleClaimDeviceObservable_DeviceAddress_Success() { + TestSubscriber sub = new TestSubscriber<>(); + String deviceAddress = "deviceAddress"; + BleClaimDeviceObservable observable = spy(new BleClaimDeviceObservable(rxFit, null, deviceAddress)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(bleApi.claimBleDevice(apiClient, deviceAddress)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void BleClaimDeviceObservable_BleDevice_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + BleDevice bleDevice = Mockito.mock(BleDevice.class); + BleClaimDeviceObservable observable = spy(new BleClaimDeviceObservable(rxFit, bleDevice, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(bleApi.claimBleDevice(apiClient, bleDevice)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void BleClaimDeviceObservable_DeviceAddress_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + String deviceAddress = "deviceAddress"; + BleClaimDeviceObservable observable = spy(new BleClaimDeviceObservable(rxFit, null, deviceAddress)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(bleApi.claimBleDevice(apiClient, deviceAddress)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + + // BleUnclaimDeviceObservable + + @Test + public void BleUnclaimDeviceObservable_BleDevice_Success() { + TestSubscriber sub = new TestSubscriber<>(); + BleDevice bleDevice = Mockito.mock(BleDevice.class); + BleUnclaimDeviceObservable observable = spy(new BleUnclaimDeviceObservable(rxFit, bleDevice, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(bleApi.unclaimBleDevice(apiClient, bleDevice)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void BleUnclaimDeviceObservable_DeviceAddress_Success() { + TestSubscriber sub = new TestSubscriber<>(); + String deviceAddress = "deviceAddress"; + BleUnclaimDeviceObservable observable = spy(new BleUnclaimDeviceObservable(rxFit, null, deviceAddress)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(bleApi.unclaimBleDevice(apiClient, deviceAddress)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void BleUnclaimDeviceObservable_BleDevice_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + BleDevice bleDevice = Mockito.mock(BleDevice.class); + BleUnclaimDeviceObservable observable = spy(new BleUnclaimDeviceObservable(rxFit, bleDevice, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(bleApi.unclaimBleDevice(apiClient, bleDevice)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void BleUnclaimDeviceObservable_DeviceAddress_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + String deviceAddress = "deviceAddress"; + BleUnclaimDeviceObservable observable = spy(new BleUnclaimDeviceObservable(rxFit, null, deviceAddress)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(bleApi.unclaimBleDevice(apiClient, deviceAddress)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // BleListClaimedDevicesObservable + + @Test + public void BleListClaimedDevicesObservable_WithDataType_Success() { + TestSubscriber> sub = new TestSubscriber<>(); + BleListClaimedDevicesObservable observable = spy(new BleListClaimedDevicesObservable(rxFit, dataType)); + + BleDevicesResult bleDevicesResult = Mockito.mock(BleDevicesResult.class); + + List bleDeviceList = new ArrayList<>(); + bleDeviceList.add(bleDevice); + + when(bleDevicesResult.getClaimedBleDevices(dataType)).thenReturn(bleDeviceList); + + setPendingResultValue(bleDevicesResult); + when(bleDevicesResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(true); + when(bleApi.listClaimedBleDevices(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, bleDeviceList); + } + + @Test + public void BleListClaimedDevicesObservable_WithDataType_StatusException() { + TestSubscriber> sub = new TestSubscriber<>(); + BleListClaimedDevicesObservable observable = spy(new BleListClaimedDevicesObservable(rxFit, dataType)); + + BleDevicesResult bleDevicesResult = Mockito.mock(BleDevicesResult.class); + + List bleDeviceList = new ArrayList<>(); + bleDeviceList.add(bleDevice); + + when(bleDevicesResult.getClaimedBleDevices(dataType)).thenReturn(bleDeviceList); + + setPendingResultValue(bleDevicesResult); + when(bleDevicesResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(false); + when(bleApi.listClaimedBleDevices(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void BleListClaimedDevicesObservable_Success() { + TestSubscriber> sub = new TestSubscriber<>(); + BleListClaimedDevicesObservable observable = spy(new BleListClaimedDevicesObservable(rxFit, null)); + + BleDevicesResult bleDevicesResult = Mockito.mock(BleDevicesResult.class); + + List bleDeviceList = new ArrayList<>(); + bleDeviceList.add(bleDevice); + + when(bleDevicesResult.getClaimedBleDevices()).thenReturn(bleDeviceList); + + setPendingResultValue(bleDevicesResult); + when(bleDevicesResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(true); + when(bleApi.listClaimedBleDevices(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, bleDeviceList); + } + + @Test + public void BleListClaimedDevicesObservable_StatusException() { + TestSubscriber> sub = new TestSubscriber<>(); + BleListClaimedDevicesObservable observable = spy(new BleListClaimedDevicesObservable(rxFit, null)); + + BleDevicesResult bleDevicesResult = Mockito.mock(BleDevicesResult.class); + + List bleDeviceList = new ArrayList<>(); + bleDeviceList.add(bleDevice); + + when(bleDevicesResult.getClaimedBleDevices()).thenReturn(bleDeviceList); + + setPendingResultValue(bleDevicesResult); + when(bleDevicesResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(false); + when(bleApi.listClaimedBleDevices(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // BleStartScanObservable + + @Test + public void BleStartScanObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + StartBleScanRequest startBleScanRequest = Mockito.mock(StartBleScanRequest.class); + BleStartScanObservable observable = spy(new BleStartScanObservable(rxFit, startBleScanRequest)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + //noinspection MissingPermission + when(bleApi.startBleScan(apiClient, startBleScanRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void BleStartScanObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + StartBleScanRequest startBleScanRequest = Mockito.mock(StartBleScanRequest.class); + BleStartScanObservable observable = spy(new BleStartScanObservable(rxFit, startBleScanRequest)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + //noinspection MissingPermission + when(bleApi.startBleScan(apiClient, startBleScanRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void BleStartScanObservable_PermissionRequiredException() throws Exception { + TestSubscriber sub = new TestSubscriber<>(); + + PowerMockito.mockStatic(ContextCompat.class); + PowerMockito.doReturn(PackageManager.PERMISSION_DENIED).when(ContextCompat.class, "checkSelfPermission", Matchers.any(Context.class), Matchers.anyString()); + + StartBleScanRequest startBleScanRequest = Mockito.mock(StartBleScanRequest.class); + //noinspection MissingPermission + BleStartScanObservable.create(rxFit, startBleScanRequest).subscribe(sub); + + assertError(sub, PermissionRequiredException.class); + } + + // BleStopScanObservable + + @Test + public void BleStopScanObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + BleScanCallback bleScanCallback = Mockito.mock(BleScanCallback.class); + BleStopScanObservable observable = spy(new BleStopScanObservable(rxFit, bleScanCallback)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + //noinspection MissingPermission + when(bleApi.stopBleScan(apiClient, bleScanCallback)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void BleStopScanObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + BleScanCallback bleScanCallback = Mockito.mock(BleScanCallback.class); + BleStopScanObservable observable = spy(new BleStopScanObservable(rxFit, bleScanCallback)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + //noinspection MissingPermission + when(bleApi.stopBleScan(apiClient, bleScanCallback)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + + /********** + * Config * + **********/ + + // ConfigCreateCustomDataTypeObservable + + @Test + public void ConfigCreateCustomDataTypeObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + DataTypeCreateRequest dataTypeCreateRequest = Mockito.mock(DataTypeCreateRequest.class); + DataTypeResult dataTypeResult = Mockito.mock(DataTypeResult.class); + ConfigCreateCustomDataTypeObservable observable = spy(new ConfigCreateCustomDataTypeObservable(rxFit, dataTypeCreateRequest)); + + setPendingResultValue(dataTypeResult); + when(dataTypeResult.getStatus()).thenReturn(status); + when(dataTypeResult.getDataType()).thenReturn(dataType); + when(status.isSuccess()).thenReturn(true); + when(configApi.createCustomDataType(apiClient, dataTypeCreateRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, dataType); + } + + @Test + public void ConfigCreateCustomDataTypeObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + DataTypeCreateRequest dataTypeCreateRequest = Mockito.mock(DataTypeCreateRequest.class); + DataTypeResult dataTypeResult = Mockito.mock(DataTypeResult.class); + ConfigCreateCustomDataTypeObservable observable = spy(new ConfigCreateCustomDataTypeObservable(rxFit, dataTypeCreateRequest)); + + setPendingResultValue(dataTypeResult); + when(dataTypeResult.getStatus()).thenReturn(status); + when(dataTypeResult.getDataType()).thenReturn(dataType); + when(status.isSuccess()).thenReturn(false); + when(configApi.createCustomDataType(apiClient, dataTypeCreateRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // ConfigDisableFitObservable + + @Test + public void ConfigDisableFitObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + ConfigDisableFitObservable observable = spy(new ConfigDisableFitObservable(rxFit)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(configApi.disableFit(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void ConfigDisableFitObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + ConfigDisableFitObservable observable = spy(new ConfigDisableFitObservable(rxFit)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(configApi.disableFit(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // ConfigReadDataTypeObservable + + @Test + public void ConfigReadDataTypeObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + String dataTypeName = "dataTypeName"; + DataTypeResult dataTypeResult = Mockito.mock(DataTypeResult.class); + ConfigReadDataTypeObservable observable = spy(new ConfigReadDataTypeObservable(rxFit, dataTypeName)); + + setPendingResultValue(dataTypeResult); + when(dataTypeResult.getStatus()).thenReturn(status); + when(dataTypeResult.getDataType()).thenReturn(dataType); + when(status.isSuccess()).thenReturn(true); + when(configApi.readDataType(apiClient, dataTypeName)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, dataType); + } + + @Test + public void ConfigReadDataTypeObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + String dataTypeName = "dataTypeName"; + DataTypeResult dataTypeResult = Mockito.mock(DataTypeResult.class); + ConfigReadDataTypeObservable observable = spy(new ConfigReadDataTypeObservable(rxFit, dataTypeName)); + + setPendingResultValue(dataTypeResult); + when(dataTypeResult.getStatus()).thenReturn(status); + when(dataTypeResult.getDataType()).thenReturn(dataType); + when(status.isSuccess()).thenReturn(false); + when(configApi.readDataType(apiClient, dataTypeName)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + /*********** + * History * + ***********/ + + // HistoryDeleteDataObservable + + @Test + public void HistoryDeleteDataObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + DataDeleteRequest dataDeleteRequest = Mockito.mock(DataDeleteRequest.class); + HistoryDeleteDataObservable observable = spy(new HistoryDeleteDataObservable(rxFit, dataDeleteRequest)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(historyApi.deleteData(apiClient, dataDeleteRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void HistoryDeleteDataObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + DataDeleteRequest dataDeleteRequest = Mockito.mock(DataDeleteRequest.class); + HistoryDeleteDataObservable observable = spy(new HistoryDeleteDataObservable(rxFit, dataDeleteRequest)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(historyApi.deleteData(apiClient, dataDeleteRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // HistoryInsertDataObservable + + @Test + public void HistoryInsertDataObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + HistoryInsertDataObservable observable = spy(new HistoryInsertDataObservable(rxFit, dataSet)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(historyApi.insertData(apiClient, dataSet)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void HistoryInsertDataObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + HistoryInsertDataObservable observable = spy(new HistoryInsertDataObservable(rxFit, dataSet)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(historyApi.insertData(apiClient, dataSet)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // HistoryReadDailyTotalObservable + + @Test + public void HistoryReadDailyTotalObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + DailyTotalResult dailyTotalResult = Mockito.mock(DailyTotalResult.class); + HistoryReadDailyTotalObservable observable = spy(new HistoryReadDailyTotalObservable(rxFit, dataType)); + + setPendingResultValue(dailyTotalResult); + when(dailyTotalResult.getStatus()).thenReturn(status); + when(dailyTotalResult.getTotal()).thenReturn(dataSet); + when(status.isSuccess()).thenReturn(true); + when(historyApi.readDailyTotal(apiClient, dataType)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, dataSet); + } + + @Test + public void HistoryReadDailyTotalObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + DailyTotalResult dailyTotalResult = Mockito.mock(DailyTotalResult.class); + HistoryReadDailyTotalObservable observable = spy(new HistoryReadDailyTotalObservable(rxFit, dataType)); + + setPendingResultValue(dailyTotalResult); + when(dailyTotalResult.getStatus()).thenReturn(status); + when(dailyTotalResult.getTotal()).thenReturn(dataSet); + when(status.isSuccess()).thenReturn(false); + when(historyApi.readDailyTotal(apiClient, dataType)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // HistoryReadDataObservable + + @Test + public void HistoryReadDataObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + DataReadRequest dataReadRequest = Mockito.mock(DataReadRequest.class); + DataReadResult dataReadResult = Mockito.mock(DataReadResult.class); + HistoryReadDataObservable observable = spy(new HistoryReadDataObservable(rxFit, dataReadRequest)); + + setPendingResultValue(dataReadResult); + when(dataReadResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(true); + when(historyApi.readData(apiClient, dataReadRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, dataReadResult); + } + + @Test + public void HistoryReadDataObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + DataReadRequest dataReadRequest = Mockito.mock(DataReadRequest.class); + DataReadResult dataReadResult = Mockito.mock(DataReadResult.class); + HistoryReadDataObservable observable = spy(new HistoryReadDataObservable(rxFit, dataReadRequest)); + + setPendingResultValue(dataReadResult); + when(dataReadResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(false); + when(historyApi.readData(apiClient, dataReadRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // HistoryUpdateDataObservable + + @Test + public void HistoryUpdateDataObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + DataUpdateRequest dataUpdateRequest = Mockito.mock(DataUpdateRequest.class); + HistoryUpdateDataObservable observable = spy(new HistoryUpdateDataObservable(rxFit, dataUpdateRequest)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(historyApi.updateData(apiClient, dataUpdateRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void HistoryUpdateDataObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + DataUpdateRequest dataUpdateRequest = Mockito.mock(DataUpdateRequest.class); + HistoryUpdateDataObservable observable = spy(new HistoryUpdateDataObservable(rxFit, dataUpdateRequest)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(historyApi.updateData(apiClient, dataUpdateRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + + /************* + * Recording * + *************/ + + // RecordingListSubscriptionsObservable + + @Test + public void RecordingListSubscriptionsObservable_Success() { + TestSubscriber> sub = new TestSubscriber<>(); + RecordingListSubscriptionsObservable observable = spy(new RecordingListSubscriptionsObservable(rxFit, null)); + + ListSubscriptionsResult listSubscriptionsResult = Mockito.mock(ListSubscriptionsResult.class); + + List subscriptionList = new ArrayList<>(); + subscriptionList.add(Mockito.mock(Subscription.class)); + + when(listSubscriptionsResult.getSubscriptions()).thenReturn(subscriptionList); + + setPendingResultValue(listSubscriptionsResult); + when(listSubscriptionsResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(true); + when(recordingApi.listSubscriptions(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, subscriptionList); + } + + @Test + public void RecordingListSubscriptionsObservable_StatusException() { + TestSubscriber> sub = new TestSubscriber<>(); + RecordingListSubscriptionsObservable observable = spy(new RecordingListSubscriptionsObservable(rxFit, null)); + + ListSubscriptionsResult listSubscriptionsResult = Mockito.mock(ListSubscriptionsResult.class); + + List subscriptionList = new ArrayList<>(); + subscriptionList.add(Mockito.mock(Subscription.class)); + + when(listSubscriptionsResult.getSubscriptions()).thenReturn(subscriptionList); + + setPendingResultValue(listSubscriptionsResult); + when(listSubscriptionsResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(false); + when(recordingApi.listSubscriptions(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void RecordingListSubscriptionsObservable_WithDataType_Success() { + TestSubscriber> sub = new TestSubscriber<>(); + RecordingListSubscriptionsObservable observable = spy(new RecordingListSubscriptionsObservable(rxFit, dataType)); + + ListSubscriptionsResult listSubscriptionsResult = Mockito.mock(ListSubscriptionsResult.class); + + List subscriptionList = new ArrayList<>(); + subscriptionList.add(Mockito.mock(Subscription.class)); + + when(listSubscriptionsResult.getSubscriptions()).thenReturn(subscriptionList); + + setPendingResultValue(listSubscriptionsResult); + when(listSubscriptionsResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(true); + when(recordingApi.listSubscriptions(apiClient, dataType)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, subscriptionList); + } + + @Test + public void RecordingListSubscriptionsObservable_WithDataType_StatusException() { + TestSubscriber> sub = new TestSubscriber<>(); + RecordingListSubscriptionsObservable observable = spy(new RecordingListSubscriptionsObservable(rxFit, dataType)); + + ListSubscriptionsResult listSubscriptionsResult = Mockito.mock(ListSubscriptionsResult.class); + + List subscriptionList = new ArrayList<>(); + subscriptionList.add(subscription); + + when(listSubscriptionsResult.getSubscriptions()).thenReturn(subscriptionList); + + setPendingResultValue(listSubscriptionsResult); + when(listSubscriptionsResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(false); + when(recordingApi.listSubscriptions(apiClient, dataType)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // RecordingSubscribeObservable + + @Test + public void RecordingSubscribeObservable_DataType_Success() { + TestSubscriber sub = new TestSubscriber<>(); + RecordingSubscribeObservable observable = spy(new RecordingSubscribeObservable(rxFit, null, dataType)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(recordingApi.subscribe(apiClient, dataType)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + + @Test + public void RecordingSubscribeObservable_DataType_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + RecordingSubscribeObservable observable = spy(new RecordingSubscribeObservable(rxFit, null, dataType)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(recordingApi.subscribe(apiClient, dataType)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void RecordingSubscribeObservable_DataSource_Success() { + TestSubscriber sub = new TestSubscriber<>(); + RecordingSubscribeObservable observable = spy(new RecordingSubscribeObservable(rxFit, dataSource, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(recordingApi.subscribe(apiClient, dataSource)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + + @Test + public void RecordingSubscribeObservable_DataSource_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + RecordingSubscribeObservable observable = spy(new RecordingSubscribeObservable(rxFit, dataSource, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(recordingApi.subscribe(apiClient, dataSource)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // RecordingSubscribeObservable + + @Test + public void RecordingUnsubscribeObservable_DataType_Success() { + TestSubscriber sub = new TestSubscriber<>(); + RecordingUnsubscribeObservable observable = spy(new RecordingUnsubscribeObservable(rxFit, null, dataType, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(recordingApi.unsubscribe(apiClient, dataType)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + + @Test + public void RecordingUnsubscribeObservable_DataType_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + RecordingUnsubscribeObservable observable = spy(new RecordingUnsubscribeObservable(rxFit, null, dataType, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(recordingApi.unsubscribe(apiClient, dataType)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void RecordingUnsubscribeObservable_DataSource_Success() { + TestSubscriber sub = new TestSubscriber<>(); + RecordingUnsubscribeObservable observable = spy(new RecordingUnsubscribeObservable(rxFit, dataSource, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(recordingApi.unsubscribe(apiClient, dataSource)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void RecordingUnsubscribeObservable_DataSource_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + RecordingUnsubscribeObservable observable = spy(new RecordingUnsubscribeObservable(rxFit, dataSource, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(recordingApi.unsubscribe(apiClient, dataSource)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void RecordingUnsubscribeObservable_Subscription_Success() { + TestSubscriber sub = new TestSubscriber<>(); + RecordingUnsubscribeObservable observable = spy(new RecordingUnsubscribeObservable(rxFit, null, null, subscription)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(recordingApi.unsubscribe(apiClient, subscription)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void RecordingUnsubscribeObservable_Subscription_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + RecordingUnsubscribeObservable observable = spy(new RecordingUnsubscribeObservable(rxFit, null, null, subscription)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(recordingApi.unsubscribe(apiClient, subscription)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + + /*********** + * Sensors * + ***********/ + + // SensorsAddDataPointIntentObservable + + @Test + public void SensorsAddDataPointIntentObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + PendingIntent pendingIntent = Mockito.mock(PendingIntent.class); + SensorsAddDataPointIntentObservable observable = spy(new SensorsAddDataPointIntentObservable(rxFit, sensorRequest, pendingIntent)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(sensorsApi.add(apiClient, sensorRequest, pendingIntent)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void SensorsAddDataPointIntentObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + PendingIntent pendingIntent = Mockito.mock(PendingIntent.class); + SensorsAddDataPointIntentObservable observable = spy(new SensorsAddDataPointIntentObservable(rxFit, sensorRequest, pendingIntent)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(sensorsApi.add(apiClient, sensorRequest, pendingIntent)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + + // SensorsRemoveDataPointIntentObservable + + @Test + public void SensorsRemoveDataPointIntentObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + PendingIntent pendingIntent = Mockito.mock(PendingIntent.class); + SensorsRemoveDataPointIntentObservable observable = spy(new SensorsRemoveDataPointIntentObservable(rxFit, pendingIntent)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(sensorsApi.remove(apiClient, pendingIntent)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void SensorsRemoveDataPointIntentObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + PendingIntent pendingIntent = Mockito.mock(PendingIntent.class); + SensorsRemoveDataPointIntentObservable observable = spy(new SensorsRemoveDataPointIntentObservable(rxFit, pendingIntent)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(sensorsApi.remove(apiClient, pendingIntent)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // SensorsDataPointObservable + + @Test + public void SensorsDataPointObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + DataPoint dataPoint = Mockito.mock(DataPoint.class); + SensorsDataPointObservable observable = spy(new SensorsDataPointObservable(rxFit, sensorRequest)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(sensorsApi.add(Matchers.any(GoogleApiClient.class), Matchers.any(SensorRequest.class), Matchers.any(OnDataPointListener.class))).thenReturn(pendingResult); + when(apiClient.isConnected()).thenReturn(true); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + observable.subscriber.onNext(dataPoint); + + verify(sensorsApi, never()).remove(Matchers.any(GoogleApiClient.class), Matchers.any(OnDataPointListener.class)); + sub.unsubscribe(); + verify(sensorsApi).remove(Matchers.any(GoogleApiClient.class), Matchers.any(OnDataPointListener.class)); + + sub.assertNoTerminalEvent(); + sub.assertValue(dataPoint); + } + + @Test + public void SensorsDataPointObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + SensorsDataPointObservable observable = spy(new SensorsDataPointObservable(rxFit, sensorRequest)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(sensorsApi.add(Matchers.any(GoogleApiClient.class), Matchers.any(SensorRequest.class), Matchers.any(OnDataPointListener.class))).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // SensorsFindDataSourcesObservable + + @Test + public void SensorsFindDataSourcesObservable_Success() { + TestSubscriber> sub = new TestSubscriber<>(); + DataSourcesRequest dataSourcesRequest = Mockito.mock(DataSourcesRequest.class); + DataSourcesResult dataSourcesResult = Mockito.mock(DataSourcesResult.class); + SensorsFindDataSourcesObservable observable = spy(new SensorsFindDataSourcesObservable(rxFit, dataSourcesRequest, null)); + + List dataSourceList = new ArrayList<>(); + dataSourceList.add(dataSource); + + when(dataSourcesResult.getDataSources()).thenReturn(dataSourceList); + + setPendingResultValue(dataSourcesResult); + when(dataSourcesResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(true); + when(sensorsApi.findDataSources(apiClient, dataSourcesRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, dataSourceList); + } + + @Test + public void SensorsFindDataSourcesObservable_StatusException() { + TestSubscriber> sub = new TestSubscriber<>(); + DataSourcesRequest dataSourcesRequest = Mockito.mock(DataSourcesRequest.class); + DataSourcesResult dataSourcesResult = Mockito.mock(DataSourcesResult.class); + SensorsFindDataSourcesObservable observable = spy(new SensorsFindDataSourcesObservable(rxFit, dataSourcesRequest, null)); + + List dataSourceList = new ArrayList<>(); + dataSourceList.add(dataSource); + + when(dataSourcesResult.getDataSources()).thenReturn(dataSourceList); + + setPendingResultValue(dataSourcesResult); + when(dataSourcesResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(false); + when(sensorsApi.findDataSources(apiClient, dataSourcesRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void SensorsFindDataSourcesObservable_WithDataType_Success() { + TestSubscriber> sub = new TestSubscriber<>(); + DataSourcesRequest dataSourcesRequest = Mockito.mock(DataSourcesRequest.class); + DataSourcesResult dataSourcesResult = Mockito.mock(DataSourcesResult.class); + SensorsFindDataSourcesObservable observable = spy(new SensorsFindDataSourcesObservable(rxFit, dataSourcesRequest, dataType)); + + List dataSourceList = new ArrayList<>(); + dataSourceList.add(dataSource); + + when(dataSourcesResult.getDataSources(dataType)).thenReturn(dataSourceList); + + setPendingResultValue(dataSourcesResult); + when(dataSourcesResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(true); + when(sensorsApi.findDataSources(apiClient, dataSourcesRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, dataSourceList); + } + + @Test + public void SensorsFindDataSourcesObservable_WithDataType_StatusException() { + TestSubscriber> sub = new TestSubscriber<>(); + DataSourcesRequest dataSourcesRequest = Mockito.mock(DataSourcesRequest.class); + DataSourcesResult dataSourcesResult = Mockito.mock(DataSourcesResult.class); + SensorsFindDataSourcesObservable observable = spy(new SensorsFindDataSourcesObservable(rxFit, dataSourcesRequest, dataType)); + + List dataSourceList = new ArrayList<>(); + dataSourceList.add(dataSource); + + when(dataSourcesResult.getDataSources(dataType)).thenReturn(dataSourceList); + + setPendingResultValue(dataSourcesResult); + when(dataSourcesResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(false); + when(sensorsApi.findDataSources(apiClient, dataSourcesRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + + /************ + * Sessions * + ************/ + + // SessionInsertObservable + + @Test + public void SessionInsertObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + SessionInsertRequest sessionInsertRequest = Mockito.mock(SessionInsertRequest.class); + SessionInsertObservable observable = spy(new SessionInsertObservable(rxFit, sessionInsertRequest)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(sessionsApi.insertSession(apiClient, sessionInsertRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void SessionInsertObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + SessionInsertRequest sessionInsertRequest = Mockito.mock(SessionInsertRequest.class); + SessionInsertObservable observable = spy(new SessionInsertObservable(rxFit, sessionInsertRequest)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(sessionsApi.insertSession(apiClient, sessionInsertRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // SessionRegisterObservable + + @Test + public void SessionRegisterObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + PendingIntent pendingIntent = Mockito.mock(PendingIntent.class); + SessionRegisterObservable observable = spy(new SessionRegisterObservable(rxFit, pendingIntent)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(sessionsApi.registerForSessions(apiClient, pendingIntent)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void SessionRegisterObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + PendingIntent pendingIntent = Mockito.mock(PendingIntent.class); + SessionRegisterObservable observable = spy(new SessionRegisterObservable(rxFit, pendingIntent)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(sessionsApi.registerForSessions(apiClient, pendingIntent)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // SessionUnregisterObservable + + @Test + public void SessionUnregisterObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + PendingIntent pendingIntent = Mockito.mock(PendingIntent.class); + SessionUnregisterObservable observable = spy(new SessionUnregisterObservable(rxFit, pendingIntent)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(sessionsApi.unregisterForSessions(apiClient, pendingIntent)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void SessionUnregisterObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + PendingIntent pendingIntent = Mockito.mock(PendingIntent.class); + SessionUnregisterObservable observable = spy(new SessionUnregisterObservable(rxFit, pendingIntent)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(sessionsApi.unregisterForSessions(apiClient, pendingIntent)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // SessionStartObservable + + @Test + public void SessionStartObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + Session session = Mockito.mock(Session.class); + SessionStartObservable observable = spy(new SessionStartObservable(rxFit, session)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(sessionsApi.startSession(apiClient, session)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void SessionStartObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + SessionStartObservable observable = spy(new SessionStartObservable(rxFit, session)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(sessionsApi.startSession(apiClient, session)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // SessionStopObservable + + @Test + public void SessionStopObservable_Success() { + TestSubscriber> sub = new TestSubscriber<>(); + String identifier = "identifier"; + SessionStopResult sessionStopResult = Mockito.mock(SessionStopResult.class); + SessionStopObservable observable = spy(new SessionStopObservable(rxFit, identifier)); + + List sessionList = new ArrayList<>(); + sessionList.add(session); + + when(sessionStopResult.getSessions()).thenReturn(sessionList); + + setPendingResultValue(sessionStopResult); + when(sessionStopResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(true); + when(sessionsApi.stopSession(apiClient, identifier)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, sessionList); + } + + @Test + public void SessionStopObservable_StatusException() { + TestSubscriber> sub = new TestSubscriber<>(); + String identifier = "identifier"; + SessionStopResult sessionStopResult = Mockito.mock(SessionStopResult.class); + SessionStopObservable observable = spy(new SessionStopObservable(rxFit, identifier)); + + List sessionList = new ArrayList<>(); + sessionList.add(session); + + when(sessionStopResult.getSessions()).thenReturn(sessionList); + + setPendingResultValue(sessionStopResult); + when(sessionStopResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(false); + when(sessionsApi.stopSession(apiClient, identifier)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // SessionReadObservable + + @Test + public void SessionReadObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + SessionReadRequest sessionReadRequest = Mockito.mock(SessionReadRequest.class); + SessionReadResult sessionReadResult = Mockito.mock(SessionReadResult.class); + SessionReadObservable observable = spy(new SessionReadObservable(rxFit, sessionReadRequest)); + + setPendingResultValue(sessionReadResult); + when(sessionReadResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(true); + when(sessionsApi.readSession(apiClient, sessionReadRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, sessionReadResult); + } + + @Test + public void SessionReadObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + SessionReadRequest sessionReadRequest = Mockito.mock(SessionReadRequest.class); + SessionReadResult sessionReadResult = Mockito.mock(SessionReadResult.class); + SessionReadObservable observable = spy(new SessionReadObservable(rxFit, sessionReadRequest)); + + setPendingResultValue(sessionReadResult); + when(sessionReadResult.getStatus()).thenReturn(status); + when(status.isSuccess()).thenReturn(false); + when(sessionsApi.readSession(apiClient, sessionReadRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + +} diff --git a/sample/.gitignore b/sample/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/sample/.gitignore @@ -0,0 +1 @@ +/build diff --git a/sample/build.gradle b/sample/build.gradle new file mode 100644 index 0000000..5df7769 --- /dev/null +++ b/sample/build.gradle @@ -0,0 +1,44 @@ +apply plugin: 'com.android.application' +apply plugin: 'me.tatarka.retrolambda' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + applicationId "com.patloew.rxfitsample" + minSdkVersion 9 + targetSdkVersion 23 + versionCode 1 + versionName "1.0.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +retrolambda { + javaVersion JavaVersion.VERSION_1_6 +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:23.2.0' + compile "com.android.support:design:23.2.0" + compile "com.android.support:recyclerview-v7:23.2.0" + compile "com.android.support:gridlayout-v7:23.2.0" + + compile project(':library') + + compile 'io.reactivex:rxjava:1.1.1' + + compile 'com.google.android.gms:play-services-fitness:8.4.0' +} diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro new file mode 100644 index 0000000..1d26e0f --- /dev/null +++ b/sample/proguard-rules.pro @@ -0,0 +1,17 @@ +# 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 *; +#} diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml new file mode 100644 index 0000000..bb9f1fc --- /dev/null +++ b/sample/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/java/com/patloew/rxfitsample/MainActivity.java b/sample/src/main/java/com/patloew/rxfitsample/MainActivity.java new file mode 100644 index 0000000..c15dbcf --- /dev/null +++ b/sample/src/main/java/com/patloew/rxfitsample/MainActivity.java @@ -0,0 +1,185 @@ +package com.patloew.rxfitsample; + +import android.os.Bundle; +import android.support.design.widget.Snackbar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.google.android.gms.common.Scopes; +import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.api.Scope; +import com.google.android.gms.fitness.Fitness; +import com.google.android.gms.fitness.data.DataType; +import com.google.android.gms.fitness.data.Field; +import com.google.android.gms.fitness.request.DataReadRequest; +import com.patloew.rxfit.RxFit; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import rx.Observable; +import rx.Subscription; + +public class MainActivity extends AppCompatActivity { + + RecyclerView recyclerView; + ProgressBar progressBar; + + ArrayList fitnessSessionDataList = new ArrayList<>(); + + Subscription subscription; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + recyclerView = (RecyclerView) findViewById(R.id.rv_main); + progressBar = (ProgressBar) findViewById(R.id.pb_main); + + recyclerView.setHasFixedSize(true); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + + RxFit.init(this, new Api[] { Fitness.SESSIONS_API, Fitness.HISTORY_API }, new Scope[] { new Scope(Scopes.FITNESS_ACTIVITY_READ) }); + + getFitnessData(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if(subscription != null && !subscription.isUnsubscribed()) { + subscription.unsubscribe(); + } + } + + private void getFitnessData() { + fitnessSessionDataList.clear(); + progressBar.setVisibility(View.VISIBLE); + recyclerView.setVisibility(View.GONE); + + DataReadRequest dataReadRequest = new DataReadRequest.Builder() + .aggregate(DataType.TYPE_STEP_COUNT_DELTA, DataType.AGGREGATE_STEP_COUNT_DELTA) + .aggregate(DataType.TYPE_CALORIES_EXPENDED, DataType.AGGREGATE_CALORIES_EXPENDED) + .bucketBySession(1, TimeUnit.MINUTES) + // 3 months back + .setTimeRange(System.currentTimeMillis() - 7776000000L, System.currentTimeMillis(), TimeUnit.MILLISECONDS) + .build(); + + subscription = RxFit.History.read(dataReadRequest) + .flatMap(dataReadResult -> Observable.from(dataReadResult.getBuckets())) + .subscribe(bucket -> { + FitnessSessionData fitnessSessionData = new FitnessSessionData(); + fitnessSessionData.name = bucket.getSession().getName(); + fitnessSessionData.appName = bucket.getSession().getAppPackageName(); + fitnessSessionData.activity = bucket.getSession().getActivity(); + fitnessSessionData.start = new Date(bucket.getSession().getStartTime(TimeUnit.MILLISECONDS)); + fitnessSessionData.end = new Date(bucket.getSession().getEndTime(TimeUnit.MILLISECONDS)); + fitnessSessionData.steps = bucket.getDataSet(DataType.AGGREGATE_STEP_COUNT_DELTA).getDataPoints().get(0).getValue(Field.FIELD_STEPS).asInt(); + fitnessSessionData.calories = (int) bucket.getDataSet(DataType.AGGREGATE_CALORIES_EXPENDED).getDataPoints().get(0).getValue(Field.FIELD_CALORIES).asFloat(); + + fitnessSessionDataList.add(fitnessSessionData); + }, e -> { + progressBar.setVisibility(View.GONE); + Log.e("MainActivity", "Error reading fitness data", e); + Snackbar.make(recyclerView, "Error getting Fit data", Snackbar.LENGTH_INDEFINITE) + .setAction("Retry", v -> getFitnessData()) + .show(); + + }, () -> { + recyclerView.setAdapter(new FitnessSessionAdapter()); + progressBar.setVisibility(View.GONE); + recyclerView.setVisibility(View.VISIBLE); + + if(fitnessSessionDataList.isEmpty()) { + Snackbar.make(recyclerView, "No sessions found", Snackbar.LENGTH_INDEFINITE).show(); + } + }); + } + + + private class FitnessSessionViewHolder extends RecyclerView.ViewHolder { + private final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + + private TextView name; + private TextView dateTimeStart; + private TextView dateTimeStop; + private TextView activity; + private TextView calories; + private TextView steps; + private ImageView icon; + + public FitnessSessionViewHolder(View itemView) { + super(itemView); + + name = (TextView) itemView.findViewById(R.id.tv_name); + dateTimeStart = (TextView) itemView.findViewById(R.id.tv_datetime_start); + dateTimeStop = (TextView) itemView.findViewById(R.id.tv_datetime_stop); + activity = (TextView) itemView.findViewById(R.id.tv_activity); + calories = (TextView) itemView.findViewById(R.id.tv_calories); + steps = (TextView) itemView.findViewById(R.id.tv_steps); + icon = (ImageView) itemView.findViewById(R.id.iv_icon); + } + + public void bind(FitnessSessionData fitnessSessionData) { + dateTimeStart.setText("Start: " + DATE_FORMAT.format(fitnessSessionData.start)); + dateTimeStop.setText("Stop: " + DATE_FORMAT.format(fitnessSessionData.end)); + calories.setText(fitnessSessionData.calories + "kcal"); + steps.setText(fitnessSessionData.steps + " steps"); + activity.setText("Activity: " + fitnessSessionData.activity); + name.setText("Session" + (!TextUtils.isEmpty(fitnessSessionData.name) ? ": " + fitnessSessionData.name : "")); + + try { + icon.setVisibility(View.VISIBLE); + icon.setImageDrawable(getPackageManager().getApplicationIcon(fitnessSessionData.appName)); + } catch(Exception ignore) { + icon.setVisibility(View.GONE); + } + } + } + + private class FitnessSessionAdapter extends RecyclerView.Adapter { + + @Override + public FitnessSessionViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View itemView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.list_item_fitness_session, parent, false); + + return new FitnessSessionViewHolder(itemView); + } + + @Override + public void onBindViewHolder(FitnessSessionViewHolder holder, int position) { + holder.bind(fitnessSessionDataList.get(position)); + } + + @Override + public int getItemCount() { + return fitnessSessionDataList.size(); + } + } + + private static class FitnessSessionData { + public Date start; + public Date end; + public String name; + public String appName; + public String activity; + public int steps; + public int calories; + + } +} 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 0000000..0d186da --- /dev/null +++ b/sample/src/main/res/layout/activity_main.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/list_item_fitness_session.xml b/sample/src/main/res/layout/list_item_fitness_session.xml new file mode 100644 index 0000000..4816cb8 --- /dev/null +++ b/sample/src/main/res/layout/list_item_fitness_session.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..cde69bc Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c133a0c Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..bfa42f0 Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..324e72c Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..aee44e1 Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/sample/src/main/res/values-w820dp/dimens.xml b/sample/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 0000000..63fc816 --- /dev/null +++ b/sample/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml new file mode 100644 index 0000000..3ab3e9c --- /dev/null +++ b/sample/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/sample/src/main/res/values/dimens.xml b/sample/src/main/res/values/dimens.xml new file mode 100644 index 0000000..47c8224 --- /dev/null +++ b/sample/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml new file mode 100644 index 0000000..5e0d8e8 --- /dev/null +++ b/sample/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + RxFit Sample + diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/sample/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..1c52b4c --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':sample', ':library' \ No newline at end of file