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/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6d5f6c4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,31 @@ +language: android +jdk: + - oraclejdk8 +android: + components: + # Uncomment the lines below if you want to + # use the latest revision of Android SDK Tools + - platform-tools + - tools + + # The BuildTools version used by your project + - build-tools-23.0.2 + + # The SDK version used to compile your project + - android-23 + + # Additional components + - extra-google-google_play_services + - extra-google-m2repository + - extra-android-m2repository + - addon-google_apis-google-23 + +before_install: + - export JAVA8_HOME=/usr/lib/jvm/java-8-oracle + - export JAVA_HOME=$JAVA8_HOME +before_cache: + - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock +cache: + directories: + - $HOME/.gradle/caches/ + - $HOME/.gradle/wrapper/ \ No newline at end of file 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..bb8e5ce --- /dev/null +++ b/NOTICE @@ -0,0 +1,46 @@ +RxWear +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..e0181b2 --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +# Reactive Wearable API Library for Android + +[![Build Status](https://travis-ci.org/patloew/RxWear.svg?branch=master)](https://travis-ci.org/patloew/RxWear) [ ![Download](https://api.bintray.com/packages/patloew/maven/com.patloew.rxwear/images/download.svg) ](https://bintray.com/patloew/maven/com.patloew.rxwear/_latestVersion) [![API](https://img.shields.io/badge/API-9%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=9) + +This library wraps the Wearable API in [RxJava](https://github.com/ReactiveX/RxJava) Observables. No more managing GoogleApiClients! Also, there are some helper classes, which ease communication between phone and wear app. + +# Usage + +Initialize RxWear once, preferably in your Application `onCreate()` via `RxWear.init(...)`. The RxWear class is very similar to the Wearable class provided by the Wearable API. Instead of `Wearable.MessageApi.sendMessage(apiClient, nodeId, path, data)` you can use `RxWear.Message.send(nodeId, path, data)`. + +There are also some helper classes to ease the putting/sending of data: +* `RxWear.Data.PutDataMap`: Use this to put a DataItem containing a DataMap to a path or a pathPrefix with an auto appended ID. +* `RxWear.Data.PutSerializable`: Use this to put a DataItem containing a Serializable object to a path or a pathPrefix with an auto appended ID. +* `RxWear.Message.SendDataMap`: Use this to send a message containing a DataMap to either one specific node or all remote nodes. +* `RxWear.Message.SendSerializable`: Use this to send a message containing a Serializable object to either one specific node or all remote nodes. + +And a few Transformers to ease fetching the data: +* `DataEventGetDataMap`: Use this Transformer to get the DataMap from a DataEvent and optionally filter the events. +* `DataEventGetSerializable`: Use this Transformer to get a Serializable object from a DataEvent and optionally filter the events. +* `DataItemGetDataMap`: Use this Transformer to get the DataMap from a DataItem and optionally filter the items. +* `DataItemGetSerializable`: Use this Transformer to get a Serializable object from a DataItems and optionally filter the items. +* `MessageEventGetDataMap`: Use this Transformer to get the DataMap from a MessageEvent and optionally filter the events. +* `MessageEventGetSerializable`: Use this Transformer to get a Serializable object from a MessageEvent and optionally filter the events. + +Example: + +```java +RxWear.init(context); + +// Phone App + +RxWear.Message.SendDataMap.toAllRemoteNodes("/dataMap") + .putString("title", "Title") + .putString("message", "My message") + .toObservable() + .subscribe(requestId -> { + /* do something */ + }); + +RxWear.Data.PutSerializable.urgentTo("/serializable", serializable) + .subscribe(dataItem -> { + /* do something */ + }); + +// Wear App + +RxWear.Message.listen() + .compose(MessageEventGetDataMap.filterByPath("/dataMap")) + .subscribe(dataMap -> { + String title = dataMap.getString("title", getString(R.string.no_message)); + String message = dataMap.getString("message", getString(R.string.no_message_info)); + /* do something */ + }); + +RxWear.Data.listen() + .compose(DataEventGetSerializable.filterByPathAndType("/serializable", DataEvent.TYPE_CHANGED)) + .subscribe(serializable -> { + /* do something */ + }); + +``` + +An optional global default timeout for all Wearable API requests made through the library can be set via `RxWear.setDefaultTimeout(...)`. In addition, timeouts can be set when creating a new Observable by providing timeout parameters, e.g. `RxWear.Message.send(nodeId, path, data, 15, TimeUnit.SECONDS)`. These parameters override the default timeout. When a timeout occurs, a StatusException is provided via `onError()`. The timeouts specified here are only used for calls to the Wearable API, e.g. a timeout will not occur when a listener does not emit an item within the specified timeout. The RxJava timeout operators can be used for this use case. + +You can also obtain an `Observable`, which connects on subscribe and disconnects on unsubscribe via `GoogleAPIClientObservable.create(...)`. + +The following Exceptions are thrown in the lib and provided via `onError()`: + +* `StatusException`: When the call to the Wearable API was not successful or timed out +* `GoogleAPIConnectionException`: When connecting to the GoogleAPIClient was not successful. +* `GoogleAPIConnectionSuspendedException`: When the GoogleApiClient connection was suspended. + +# Sample + +A basic sample app is available in the `sample` and `wearsample` projects. + +# Setup + +Add the following to your `build.gradle`: + + repositories { + maven { url 'https://dl.bintray.com/patloew/maven' } + } + + dependencies { + compile 'com.patloew.rxwear:rxwear:1.0.0' + } + +# 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..ac01455 --- /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.6' + 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..3a0793c --- /dev/null +++ b/library/build.gradle @@ -0,0 +1,63 @@ +apply plugin: 'com.android.library' +apply plugin: 'com.jfrog.bintray' +apply plugin: 'com.github.dcendents.android-maven' + +group = 'com.patloew.rxwear' +version = '1.0.0' +project.archivesBaseName = 'rxwear' + +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-wearable: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..76e6adf --- /dev/null +++ b/library/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/library/src/main/java/com/patloew/rxwear/BaseObservable.java b/library/src/main/java/com/patloew/rxwear/BaseObservable.java new file mode 100644 index 0000000..1f075a0 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/BaseObservable.java @@ -0,0 +1,169 @@ +package com.patloew.rxwear; + +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.api.Api; +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.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.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +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 final Context ctx; + private final Api[] services; + private final Scope[] scopes; + private final Long timeoutTime; + private final TimeUnit timeoutUnit; + + protected BaseObservable(@NonNull RxWear rxWear, Long timeout, TimeUnit timeUnit) { + this.ctx = rxWear.getContext(); + this.services = new Api[] {Wearable.API }; + this.scopes = null; + + if(timeout != null && timeUnit != null) { + this.timeoutTime = timeout; + this.timeoutUnit = timeUnit; + } else { + this.timeoutTime = RxWear.getDefaultTimeout(); + this.timeoutUnit = RxWear.getDefaultTimeoutUnit(); + } + } + + protected BaseObservable(@NonNull Context ctx, @NonNull Api[] services, Scope[] scopes) { + this.ctx = ctx; + this.services = services; + this.scopes = scopes; + timeoutTime = null; + timeoutUnit = null; + } + + protected final void setupWearPendingResult(PendingResult pendingResult, ResultCallback resultCallback) { + if(timeoutTime != null && timeoutUnit != null) { + pendingResult.setResultCallback(resultCallback, timeoutTime, timeoutUnit); + } else { + pendingResult.setResultCallback(resultCallback); + } + } + + @Override + public final void call(Subscriber subscriber) { + final GoogleApiClient 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(); + } + } + })); + } + + + 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) { + observer.onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient.", connectionResult)); + } + + public void setClient(GoogleApiClient client) { + this.apiClient = client; + } + } +} diff --git a/library/src/main/java/com/patloew/rxwear/CapabilityAddLocalObservable.java b/library/src/main/java/com/patloew/rxwear/CapabilityAddLocalObservable.java new file mode 100644 index 0000000..6ca3c7a --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/CapabilityAddLocalObservable.java @@ -0,0 +1,51 @@ +package com.patloew.rxwear; + +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.wearable.CapabilityApi; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class CapabilityAddLocalObservable extends BaseObservable { + + private final String capability; + + CapabilityAddLocalObservable(RxWear rxWear, String capability, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.capability = capability; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + setupWearPendingResult(Wearable.CapabilityApi.addLocalCapability(apiClient, capability), new ResultCallback() { + @Override + public void onResult(@NonNull CapabilityApi.AddLocalCapabilityResult addLocalCapabilityResult) { + if (!addLocalCapabilityResult.getStatus().isSuccess()) { + observer.onError(new StatusException(addLocalCapabilityResult.getStatus())); + } else { + observer.onNext(addLocalCapabilityResult.getStatus()); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/CapabilityGetAllObservable.java b/library/src/main/java/com/patloew/rxwear/CapabilityGetAllObservable.java new file mode 100644 index 0000000..ae37d6e --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/CapabilityGetAllObservable.java @@ -0,0 +1,52 @@ +package com.patloew.rxwear; + +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.wearable.CapabilityApi; +import com.google.android.gms.wearable.CapabilityInfo; +import com.google.android.gms.wearable.Wearable; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class CapabilityGetAllObservable extends BaseObservable> { + + private final int nodeFilter; + + CapabilityGetAllObservable(RxWear rxWear, int nodeFilter, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.nodeFilter = nodeFilter; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer> observer) { + setupWearPendingResult(Wearable.CapabilityApi.getAllCapabilities(apiClient, nodeFilter), new ResultCallback() { + @Override + public void onResult(@NonNull CapabilityApi.GetAllCapabilitiesResult getAllCapabilitiesResult) { + if (!getAllCapabilitiesResult.getStatus().isSuccess()) { + observer.onError(new StatusException(getAllCapabilitiesResult.getStatus())); + } else { + observer.onNext(getAllCapabilitiesResult.getAllCapabilities()); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/CapabilityGetObservable.java b/library/src/main/java/com/patloew/rxwear/CapabilityGetObservable.java new file mode 100644 index 0000000..f7c2e31 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/CapabilityGetObservable.java @@ -0,0 +1,53 @@ +package com.patloew.rxwear; + +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.wearable.CapabilityApi; +import com.google.android.gms.wearable.CapabilityInfo; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class CapabilityGetObservable extends BaseObservable { + + private final String capability; + private final int nodeFilter; + + CapabilityGetObservable(RxWear rxWear, String capability, int nodeFilter, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.capability = capability; + this.nodeFilter = nodeFilter; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + setupWearPendingResult(Wearable.CapabilityApi.getCapability(apiClient, capability, nodeFilter), new ResultCallback() { + @Override + public void onResult(@NonNull CapabilityApi.GetCapabilityResult getCapabilitiesResult) { + if (!getCapabilitiesResult.getStatus().isSuccess()) { + observer.onError(new StatusException(getCapabilitiesResult.getStatus())); + } else { + observer.onNext(getCapabilitiesResult.getCapability()); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/CapabilityListenerObservable.java b/library/src/main/java/com/patloew/rxwear/CapabilityListenerObservable.java new file mode 100644 index 0000000..db63253 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/CapabilityListenerObservable.java @@ -0,0 +1,79 @@ +package com.patloew.rxwear; + +import android.net.Uri; +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.wearable.CapabilityApi; +import com.google.android.gms.wearable.CapabilityInfo; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class CapabilityListenerObservable extends BaseObservable { + + private final String capability; + private final Uri uri; + private final Integer filterType; + + private CapabilityApi.CapabilityListener listener; + + CapabilityListenerObservable(RxWear rxWear, String capability, Uri uri, Integer filterType, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.capability = capability; + this.uri = uri; + this.filterType = filterType; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + listener = new CapabilityApi.CapabilityListener() { + @Override + public void onCapabilityChanged(CapabilityInfo capabilityInfo) { + observer.onNext(capabilityInfo); + } + }; + + ResultCallback resultCallback = new ResultCallback() { + @Override + public void onResult(@NonNull Status status) { + if (!status.isSuccess()) { + observer.onError(new StatusException(status)); + } + } + }; + + if(capability != null) { + setupWearPendingResult(Wearable.CapabilityApi.addCapabilityListener(apiClient, listener, capability), resultCallback); + } else { + setupWearPendingResult(Wearable.CapabilityApi.addListener(apiClient, listener, uri, filterType), resultCallback); + } + } + + + @Override + protected void onUnsubscribed(GoogleApiClient apiClient) { + if(capability != null) { + Wearable.CapabilityApi.removeCapabilityListener(apiClient, listener, capability); + } else { + Wearable.CapabilityApi.removeListener(apiClient, listener); + } + } +} diff --git a/library/src/main/java/com/patloew/rxwear/CapabilityRemoveLocalObservable.java b/library/src/main/java/com/patloew/rxwear/CapabilityRemoveLocalObservable.java new file mode 100644 index 0000000..12c15ae --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/CapabilityRemoveLocalObservable.java @@ -0,0 +1,51 @@ +package com.patloew.rxwear; + +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.wearable.CapabilityApi; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class CapabilityRemoveLocalObservable extends BaseObservable { + + private final String capability; + + CapabilityRemoveLocalObservable(RxWear rxWear, String capability, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.capability = capability; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + setupWearPendingResult(Wearable.CapabilityApi.removeLocalCapability(apiClient, capability), new ResultCallback() { + @Override + public void onResult(@NonNull CapabilityApi.RemoveLocalCapabilityResult removeLocalCapabilityResult) { + if (!removeLocalCapabilityResult.getStatus().isSuccess()) { + observer.onError(new StatusException(removeLocalCapabilityResult.getStatus())); + } else { + observer.onNext(removeLocalCapabilityResult.getStatus()); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/ChannelCloseObservable.java b/library/src/main/java/com/patloew/rxwear/ChannelCloseObservable.java new file mode 100644 index 0000000..4228a18 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/ChannelCloseObservable.java @@ -0,0 +1,45 @@ +package com.patloew.rxwear; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.wearable.Channel; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class ChannelCloseObservable extends BaseObservable { + + private final Channel channel; + private final Integer errorCode; + + ChannelCloseObservable(RxWear rxWear, Channel channel, Integer errorCode, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.channel = channel; + this.errorCode = errorCode; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + StatusResultCallBack resultCallBack = new StatusResultCallBack(observer); + + if(errorCode != null) { + setupWearPendingResult(channel.close(apiClient, errorCode), resultCallBack); + } else { + setupWearPendingResult(channel.close(apiClient), resultCallBack); + } + } +} diff --git a/library/src/main/java/com/patloew/rxwear/ChannelGetInputStreamObservable.java b/library/src/main/java/com/patloew/rxwear/ChannelGetInputStreamObservable.java new file mode 100644 index 0000000..a1912a0 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/ChannelGetInputStreamObservable.java @@ -0,0 +1,50 @@ +package com.patloew.rxwear; + +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.wearable.Channel; + +import java.io.InputStream; +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class ChannelGetInputStreamObservable extends BaseObservable { + + private final Channel channel; + + ChannelGetInputStreamObservable(RxWear rxWear, Channel channel, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.channel = channel; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + setupWearPendingResult(channel.getInputStream(apiClient), new ResultCallback() { + @Override + public void onResult(@NonNull Channel.GetInputStreamResult getInputStreamResult) { + if (!getInputStreamResult.getStatus().isSuccess()) { + observer.onError(new StatusException(getInputStreamResult.getStatus())); + } else { + observer.onNext(getInputStreamResult.getInputStream()); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/ChannelGetOutputStreamObservable.java b/library/src/main/java/com/patloew/rxwear/ChannelGetOutputStreamObservable.java new file mode 100644 index 0000000..bb181a6 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/ChannelGetOutputStreamObservable.java @@ -0,0 +1,50 @@ +package com.patloew.rxwear; + +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.wearable.Channel; + +import java.io.OutputStream; +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class ChannelGetOutputStreamObservable extends BaseObservable { + + private final Channel channel; + + ChannelGetOutputStreamObservable(RxWear rxWear, Channel channel, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.channel = channel; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + setupWearPendingResult(channel.getOutputStream(apiClient), new ResultCallback() { + @Override + public void onResult(@NonNull Channel.GetOutputStreamResult getOutputStreamResult) { + if (!getOutputStreamResult.getStatus().isSuccess()) { + observer.onError(new StatusException(getOutputStreamResult.getStatus())); + } else { + observer.onNext(getOutputStreamResult.getOutputStream()); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/ChannelListenerObservable.java b/library/src/main/java/com/patloew/rxwear/ChannelListenerObservable.java new file mode 100644 index 0000000..f7aab85 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/ChannelListenerObservable.java @@ -0,0 +1,94 @@ +package com.patloew.rxwear; + +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.wearable.Channel; +import com.google.android.gms.wearable.ChannelApi; +import com.google.android.gms.wearable.Wearable; +import com.patloew.rxwear.events.ChannelClosedEvent; +import com.patloew.rxwear.events.ChannelEvent; +import com.patloew.rxwear.events.ChannelOpenedEvent; +import com.patloew.rxwear.events.InputClosedEvent; +import com.patloew.rxwear.events.OutputClosedEvent; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class ChannelListenerObservable extends BaseObservable { + + private final Channel channel; + private ChannelApi.ChannelListener listener; + + ChannelListenerObservable(RxWear rxWear, Channel channel, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.channel = channel; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + listener = new ChannelApi.ChannelListener() { + + @Override + public void onChannelOpened(Channel channel) { + observer.onNext(new ChannelOpenedEvent(channel)); + } + + @Override + public void onChannelClosed(Channel channel, int closeReason, int appSpecificErrorCode) { + observer.onNext(new ChannelClosedEvent(channel, closeReason, appSpecificErrorCode)); + } + + @Override + public void onInputClosed(Channel channel, int closeReason, int appSpecificErrorCode) { + observer.onNext(new InputClosedEvent(channel, closeReason, appSpecificErrorCode)); + } + + @Override + public void onOutputClosed(Channel channel, int closeReason, int appSpecificErrorCode) { + observer.onNext(new OutputClosedEvent(channel, closeReason, appSpecificErrorCode)); + } + }; + + ResultCallback resultCallback = new ResultCallback() { + @Override + public void onResult(@NonNull Status status) { + if (!status.isSuccess()) { + observer.onError(new StatusException(status)); + } + } + }; + + if(channel != null) { + setupWearPendingResult(channel.addListener(apiClient, listener), resultCallback); + } else { + setupWearPendingResult(Wearable.ChannelApi.addListener(apiClient, listener), resultCallback); + } + } + + + @Override + protected void onUnsubscribed(GoogleApiClient apiClient) { + if(channel != null) { + channel.removeListener(apiClient, listener); + } else { + Wearable.ChannelApi.removeListener(apiClient, listener); + } + } +} diff --git a/library/src/main/java/com/patloew/rxwear/ChannelOpenObservable.java b/library/src/main/java/com/patloew/rxwear/ChannelOpenObservable.java new file mode 100644 index 0000000..f8130e3 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/ChannelOpenObservable.java @@ -0,0 +1,56 @@ +package com.patloew.rxwear; + +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.wearable.Channel; +import com.google.android.gms.wearable.ChannelApi; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class ChannelOpenObservable extends BaseObservable { + + private final String nodeId; + private final String path; + + ChannelOpenObservable(RxWear rxWear, String nodeId, String path, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.nodeId = nodeId; + this.path = path; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + setupWearPendingResult(Wearable.ChannelApi.openChannel(apiClient, nodeId, path), new ResultCallback() { + @Override + public void onResult(@NonNull ChannelApi.OpenChannelResult openChannelResult) { + if (!openChannelResult.getStatus().isSuccess()) { + observer.onError(new StatusException(openChannelResult.getStatus())); + } else { + if(openChannelResult.getChannel() != null) { + observer.onNext(openChannelResult.getChannel()); + } + + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/ChannelReceiveFileObservable.java b/library/src/main/java/com/patloew/rxwear/ChannelReceiveFileObservable.java new file mode 100644 index 0000000..e27902f --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/ChannelReceiveFileObservable.java @@ -0,0 +1,43 @@ +package com.patloew.rxwear; + +import android.net.Uri; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.wearable.Channel; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class ChannelReceiveFileObservable extends BaseObservable { + + private final Channel channel; + private final Uri uri; + private final boolean append; + + ChannelReceiveFileObservable(RxWear rxWear, Channel channel, Uri uri, boolean append, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.channel = channel; + this.uri = uri; + this.append = append; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + setupWearPendingResult(channel.receiveFile(apiClient, uri, append), new StatusResultCallBack(observer)); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/ChannelSendFileObservable.java b/library/src/main/java/com/patloew/rxwear/ChannelSendFileObservable.java new file mode 100644 index 0000000..4a3f9a7 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/ChannelSendFileObservable.java @@ -0,0 +1,51 @@ +package com.patloew.rxwear; + +import android.net.Uri; + +import com.google.android.gms.common.api.GoogleApiClient; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.wearable.Channel; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class ChannelSendFileObservable extends BaseObservable { + + private final Channel channel; + private final Uri uri; + private final Long startOffset; + private final Long length; + + ChannelSendFileObservable(RxWear rxWear, Channel channel, Uri uri, Long startOffset, Long length, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.channel = channel; + this.uri = uri; + this.startOffset = startOffset; + this.length = length; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + StatusResultCallBack resultCallBack = new StatusResultCallBack(observer); + + if(startOffset != null && length != null) { + setupWearPendingResult(channel.sendFile(apiClient, uri, startOffset, length), resultCallBack); + } else { + setupWearPendingResult(channel.sendFile(apiClient, uri), resultCallBack); + } + } +} diff --git a/library/src/main/java/com/patloew/rxwear/CheckConnectionObservable.java b/library/src/main/java/com/patloew/rxwear/CheckConnectionObservable.java new file mode 100644 index 0000000..502a3c2 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/CheckConnectionObservable.java @@ -0,0 +1,30 @@ +package com.patloew.rxwear; + +import com.google.android.gms.common.api.GoogleApiClient; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class CheckConnectionObservable extends BaseObservable { + + CheckConnectionObservable(RxWear rxWear) { + super(rxWear, null, null); + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, Observer observer) { + observer.onCompleted(); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/DataDeleteItemsObservable.java b/library/src/main/java/com/patloew/rxwear/DataDeleteItemsObservable.java new file mode 100644 index 0000000..f2f0244 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/DataDeleteItemsObservable.java @@ -0,0 +1,59 @@ +package com.patloew.rxwear; + +import android.net.Uri; +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.wearable.DataApi; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class DataDeleteItemsObservable extends BaseObservable { + + private final Uri uri; + private final Integer filterType; + + DataDeleteItemsObservable(RxWear rxWear, Uri uri, Integer filterType, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.uri = uri; + this.filterType = filterType; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + ResultCallback resultResultCallback = new ResultCallback() { + @Override + public void onResult(@NonNull DataApi.DeleteDataItemsResult deleteDataItemsResult) { + if (!deleteDataItemsResult.getStatus().isSuccess()) { + observer.onError(new StatusException(deleteDataItemsResult.getStatus())); + } else { + observer.onNext(deleteDataItemsResult.getNumDeleted()); + observer.onCompleted(); + } + } + }; + + if(filterType == null) { + setupWearPendingResult(Wearable.DataApi.deleteDataItems(apiClient, uri), resultResultCallback); + } else { + setupWearPendingResult(Wearable.DataApi.deleteDataItems(apiClient, uri, filterType), resultResultCallback); + } + } +} diff --git a/library/src/main/java/com/patloew/rxwear/DataGetFdForAssetObservable.java b/library/src/main/java/com/patloew/rxwear/DataGetFdForAssetObservable.java new file mode 100644 index 0000000..3fd2d14 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/DataGetFdForAssetObservable.java @@ -0,0 +1,60 @@ +package com.patloew.rxwear; + +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.wearable.Asset; +import com.google.android.gms.wearable.DataApi; +import com.google.android.gms.wearable.DataItemAsset; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class DataGetFdForAssetObservable extends BaseObservable { + + private final DataItemAsset dataItemAsset; + private final Asset asset; + + DataGetFdForAssetObservable(RxWear rxWear, DataItemAsset dataItemAsset, Asset asset, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.dataItemAsset = dataItemAsset; + this.asset = asset; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + ResultCallback resultCallback = new ResultCallback() { + @Override + public void onResult(@NonNull DataApi.GetFdForAssetResult getFdForAssetResult) { + if (!getFdForAssetResult.getStatus().isSuccess()) { + observer.onError(new StatusException(getFdForAssetResult.getStatus())); + } else { + observer.onNext(getFdForAssetResult); + observer.onCompleted(); + } + } + }; + + if(asset != null) { + setupWearPendingResult(Wearable.DataApi.getFdForAsset(apiClient, asset), resultCallback); + } else { + setupWearPendingResult(Wearable.DataApi.getFdForAsset(apiClient, dataItemAsset), resultCallback); + } + } +} diff --git a/library/src/main/java/com/patloew/rxwear/DataGetItemObservable.java b/library/src/main/java/com/patloew/rxwear/DataGetItemObservable.java new file mode 100644 index 0000000..02c86b0 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/DataGetItemObservable.java @@ -0,0 +1,52 @@ +package com.patloew.rxwear; + +import android.net.Uri; +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.wearable.DataApi; +import com.google.android.gms.wearable.DataItem; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class DataGetItemObservable extends BaseObservable { + + private final Uri uri; + + DataGetItemObservable(RxWear rxWear, Uri uri, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.uri = uri; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + setupWearPendingResult(Wearable.DataApi.getDataItem(apiClient, uri), new ResultCallback() { + @Override + public void onResult(@NonNull DataApi.DataItemResult dataItemResult) { + if (!dataItemResult.getStatus().isSuccess()) { + observer.onError(new StatusException(dataItemResult.getStatus())); + } else { + observer.onNext(dataItemResult.getDataItem().freeze()); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/DataGetItemsObservable.java b/library/src/main/java/com/patloew/rxwear/DataGetItemsObservable.java new file mode 100644 index 0000000..119455e --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/DataGetItemsObservable.java @@ -0,0 +1,73 @@ +package com.patloew.rxwear; + +import android.net.Uri; +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.wearable.DataItem; +import com.google.android.gms.wearable.DataItemBuffer; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class DataGetItemsObservable extends BaseObservable { + + private final Uri uri; + private final Integer filterType; + + DataGetItemsObservable(RxWear rxWear, Uri uri, Integer filterType, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.uri = uri; + this.filterType = filterType; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + ResultCallback resultCallback = new ResultCallback() { + @Override + public void onResult(@NonNull DataItemBuffer dataItemBuffer) { + try { + if(!dataItemBuffer.getStatus().isSuccess()) { + observer.onError(new StatusException(dataItemBuffer.getStatus())); + } else { + for (int i = 0; i < dataItemBuffer.getCount(); i++) { + observer.onNext(dataItemBuffer.get(i).freeze()); + } + + observer.onCompleted(); + } + } catch(Throwable throwable) { + observer.onError(throwable); + } finally { + dataItemBuffer.release(); + } + } + }; + + if(uri == null) { + setupWearPendingResult(Wearable.DataApi.getDataItems(apiClient), resultCallback); + } else { + if(filterType == null) { + setupWearPendingResult(Wearable.DataApi.getDataItems(apiClient, uri), resultCallback); + } else { + setupWearPendingResult(Wearable.DataApi.getDataItems(apiClient, uri, filterType), resultCallback); + } + } + } +} diff --git a/library/src/main/java/com/patloew/rxwear/DataListenerObservable.java b/library/src/main/java/com/patloew/rxwear/DataListenerObservable.java new file mode 100644 index 0000000..901c493 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/DataListenerObservable.java @@ -0,0 +1,76 @@ +package com.patloew.rxwear; + +import android.net.Uri; +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.wearable.DataApi; +import com.google.android.gms.wearable.DataEvent; +import com.google.android.gms.wearable.DataEventBuffer; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class DataListenerObservable extends BaseObservable { + + private final Uri uri; + private final Integer filterType; + + private DataApi.DataListener listener; + + DataListenerObservable(RxWear rxWear, Uri uri, Integer filterType, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.uri = uri; + this.filterType = filterType; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + listener = new DataApi.DataListener() { + @Override + public void onDataChanged(DataEventBuffer dataEventBuffer) { + for(int i=0; i resultCallback = new ResultCallback() { + @Override + public void onResult(@NonNull Status status) { + if (!status.isSuccess()) { + observer.onError(new StatusException(status)); + } + } + }; + + if(uri != null && filterType != null) { + setupWearPendingResult(Wearable.DataApi.addListener(apiClient, listener, uri, filterType), resultCallback); + } else { + setupWearPendingResult(Wearable.DataApi.addListener(apiClient, listener), resultCallback); + } + } + + + @Override + protected void onUnsubscribed(GoogleApiClient apiClient) { + Wearable.DataApi.removeListener(apiClient, listener); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/DataPutItemObservable.java b/library/src/main/java/com/patloew/rxwear/DataPutItemObservable.java new file mode 100644 index 0000000..6bb1e99 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/DataPutItemObservable.java @@ -0,0 +1,52 @@ +package com.patloew.rxwear; + +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.wearable.DataApi; +import com.google.android.gms.wearable.DataItem; +import com.google.android.gms.wearable.PutDataRequest; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class DataPutItemObservable extends BaseObservable { + + private final PutDataRequest putDataRequest; + + DataPutItemObservable(RxWear rxWear, PutDataRequest putDataRequest, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.putDataRequest = putDataRequest; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + setupWearPendingResult(Wearable.DataApi.putDataItem(apiClient, putDataRequest), new ResultCallback() { + @Override + public void onResult(@NonNull DataApi.DataItemResult dataItemResult) { + if (!dataItemResult.getStatus().isSuccess()) { + observer.onError(new StatusException(dataItemResult.getStatus())); + } else { + observer.onNext(dataItemResult.getDataItem()); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/GoogleAPIClientObservable.java b/library/src/main/java/com/patloew/rxwear/GoogleAPIClientObservable.java new file mode 100644 index 0000000..ca4b2fe --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/GoogleAPIClientObservable.java @@ -0,0 +1,52 @@ +package com.patloew.rxwear; + +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/rxwear/GoogleAPIConnectionException.java b/library/src/main/java/com/patloew/rxwear/GoogleAPIConnectionException.java new file mode 100644 index 0000000..4416bf1 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/GoogleAPIConnectionException.java @@ -0,0 +1,38 @@ +package com.patloew.rxwear; + +import com.google.android.gms.common.ConnectionResult; + +/* Copyright (C) 2015 Michał Charmas (http://blog.charmas.pl) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +public class GoogleAPIConnectionException extends RuntimeException { + private final ConnectionResult connectionResult; + + GoogleAPIConnectionException(String detailMessage, ConnectionResult connectionResult) { + super(detailMessage); + this.connectionResult = connectionResult; + } + + public ConnectionResult getConnectionResult() { + return connectionResult; + } + + public boolean wasResolutionUnsuccessful() { + if(connectionResult != null) { + return connectionResult.hasResolution(); + } else { + return false; + } + } +} diff --git a/library/src/main/java/com/patloew/rxwear/GoogleAPIConnectionSuspendedException.java b/library/src/main/java/com/patloew/rxwear/GoogleAPIConnectionSuspendedException.java new file mode 100644 index 0000000..931e6d6 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/GoogleAPIConnectionSuspendedException.java @@ -0,0 +1,27 @@ +package com.patloew.rxwear; + +/* 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/rxwear/MessageListenerObservable.java b/library/src/main/java/com/patloew/rxwear/MessageListenerObservable.java new file mode 100644 index 0000000..428e8ef --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/MessageListenerObservable.java @@ -0,0 +1,73 @@ +package com.patloew.rxwear; + +import android.net.Uri; +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.wearable.MessageApi; +import com.google.android.gms.wearable.MessageEvent; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class MessageListenerObservable extends BaseObservable { + + private final Uri uri; + private final Integer filterType; + + private MessageApi.MessageListener listener; + + MessageListenerObservable(RxWear rxWear, Uri uri, Integer filterType, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.uri = uri; + this.filterType = filterType; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + listener = new MessageApi.MessageListener() { + @Override + public void onMessageReceived(MessageEvent messageEvent) { + observer.onNext(messageEvent); + } + }; + + ResultCallback resultCallback = new ResultCallback() { + @Override + public void onResult(@NonNull Status status) { + if (!status.isSuccess()) { + observer.onError(new StatusException(status)); + } + } + }; + + if(uri != null) { + setupWearPendingResult(Wearable.MessageApi.addListener(apiClient, listener, uri, filterType), resultCallback); + } else { + setupWearPendingResult(Wearable.MessageApi.addListener(apiClient, listener), resultCallback); + } + } + + + @Override + protected void onUnsubscribed(GoogleApiClient apiClient) { + Wearable.MessageApi.removeListener(apiClient, listener); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/MessageSendObservable.java b/library/src/main/java/com/patloew/rxwear/MessageSendObservable.java new file mode 100644 index 0000000..f766c41 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/MessageSendObservable.java @@ -0,0 +1,54 @@ +package com.patloew.rxwear; + +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.wearable.MessageApi; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class MessageSendObservable extends BaseObservable { + + private final String nodeId; + private final String path; + private final byte[] data; + + MessageSendObservable(RxWear rxWear, String nodeId, String path, byte[] data, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + this.nodeId = nodeId; + this.path = path; + this.data = data; + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + setupWearPendingResult(Wearable.MessageApi.sendMessage(apiClient, nodeId, path, data), new ResultCallback() { + @Override + public void onResult(@NonNull MessageApi.SendMessageResult sendMessageResult) { + if (!sendMessageResult.getStatus().isSuccess()) { + observer.onError(new StatusException(sendMessageResult.getStatus())); + } else { + observer.onNext(sendMessageResult.getRequestId()); + observer.onCompleted(); + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/NodeGetConnectedObservable.java b/library/src/main/java/com/patloew/rxwear/NodeGetConnectedObservable.java new file mode 100644 index 0000000..0e3ff31 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/NodeGetConnectedObservable.java @@ -0,0 +1,50 @@ +package com.patloew.rxwear; + +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.wearable.Node; +import com.google.android.gms.wearable.NodeApi; +import com.google.android.gms.wearable.Wearable; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class NodeGetConnectedObservable extends BaseObservable> { + + NodeGetConnectedObservable(RxWear rxWear, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer> observer) { + setupWearPendingResult(Wearable.NodeApi.getConnectedNodes(apiClient), new ResultCallback() { + @Override + public void onResult(@NonNull NodeApi.GetConnectedNodesResult getConnectedNodesResult) { + if (!getConnectedNodesResult.getStatus().isSuccess()) { + observer.onError(new StatusException(getConnectedNodesResult.getStatus())); + } else { + observer.onNext(getConnectedNodesResult.getNodes()); + observer.onCompleted(); + } + } + }); + + } +} diff --git a/library/src/main/java/com/patloew/rxwear/NodeGetLocalObservable.java b/library/src/main/java/com/patloew/rxwear/NodeGetLocalObservable.java new file mode 100644 index 0000000..fdb04d5 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/NodeGetLocalObservable.java @@ -0,0 +1,49 @@ +package com.patloew.rxwear; + +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.wearable.Node; +import com.google.android.gms.wearable.NodeApi; +import com.google.android.gms.wearable.Wearable; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class NodeGetLocalObservable extends BaseObservable { + + NodeGetLocalObservable(RxWear rxWear, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + setupWearPendingResult(Wearable.NodeApi.getLocalNode(apiClient), new ResultCallback() { + @Override + public void onResult(@NonNull NodeApi.GetLocalNodeResult getLocalNodeResult) { + if (!getLocalNodeResult.getStatus().isSuccess()) { + observer.onError(new StatusException(getLocalNodeResult.getStatus())); + } else { + observer.onNext(getLocalNodeResult.getNode()); + observer.onCompleted(); + } + } + }); + + } +} diff --git a/library/src/main/java/com/patloew/rxwear/NodeListenerObservable.java b/library/src/main/java/com/patloew/rxwear/NodeListenerObservable.java new file mode 100644 index 0000000..4384555 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/NodeListenerObservable.java @@ -0,0 +1,70 @@ +package com.patloew.rxwear; + +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.wearable.Node; +import com.google.android.gms.wearable.NodeApi; +import com.google.android.gms.wearable.Wearable; +import com.patloew.rxwear.events.NodeEvent; + +import java.util.concurrent.TimeUnit; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class NodeListenerObservable extends BaseObservable { + + + private NodeApi.NodeListener listener; + + NodeListenerObservable(RxWear rxWear, Long timeout, TimeUnit timeUnit) { + super(rxWear, timeout, timeUnit); + } + + @Override + protected void onGoogleApiClientReady(GoogleApiClient apiClient, final Observer observer) { + listener = new NodeApi.NodeListener() { + @Override + public void onPeerConnected(Node node) { + observer.onNext(new NodeEvent(node, true)); + } + + @Override + public void onPeerDisconnected(Node node) { + observer.onNext(new NodeEvent(node, false)); + } + }; + + ResultCallback resultCallback = new ResultCallback() { + @Override + public void onResult(@NonNull Status status) { + if (!status.isSuccess()) { + observer.onError(new StatusException(status)); + } + } + }; + + setupWearPendingResult(Wearable.NodeApi.addListener(apiClient, listener), resultCallback); + } + + + @Override + protected void onUnsubscribed(GoogleApiClient apiClient) { + Wearable.NodeApi.removeListener(apiClient, listener); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/RxWear.java b/library/src/main/java/com/patloew/rxwear/RxWear.java new file mode 100644 index 0000000..d895935 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/RxWear.java @@ -0,0 +1,962 @@ +package com.patloew.rxwear; + +import android.content.Context; +import android.net.Uri; +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.wearable.Asset; +import com.google.android.gms.wearable.CapabilityInfo; +import com.google.android.gms.wearable.DataApi; +import com.google.android.gms.wearable.DataEvent; +import com.google.android.gms.wearable.DataItem; +import com.google.android.gms.wearable.DataItemAsset; +import com.google.android.gms.wearable.DataMap; +import com.google.android.gms.wearable.DataMapItem; +import com.google.android.gms.wearable.MessageEvent; +import com.google.android.gms.wearable.PutDataMapRequest; +import com.google.android.gms.wearable.PutDataRequest; +import com.google.android.gms.wearable.Wearable; +import com.patloew.rxwear.events.ChannelEvent; +import com.patloew.rxwear.events.NodeEvent; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import rx.Completable; +import rx.Observable; +import rx.functions.Func1; +import rx.functions.Func2; + +/* 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. + * + * ------------------------------- + * + * Factory for Google Wearable API observables. + * + */ +public class RxWear { + + private static RxWear instance = null; + + private static @NonNull Long timeoutTime = null; + private static TimeUnit timeoutUnit = null; + + private final Context ctx; + + /* Initializes the singleton instance of RxWear + * + * @param ctx Context. + */ + public static void init(@NonNull Context ctx) { + if(instance == null) { instance = new RxWear(ctx); } + } + + /* Set a default timeout for all requests to the Wearable API made in the lib. + * When a timeout occurs, onError() is called with a StatusException. + */ + public static void setDefaultTimeout(long time, @NonNull TimeUnit timeUnit) { + if(timeUnit != null) { + timeoutTime = time; + timeoutUnit = timeUnit; + } else { + throw new IllegalArgumentException("timeUnit parameter must not be null"); + } + } + + /* Reset the default timeout. + */ + public static void resetDefaultTimeout() { + timeoutTime = null; + timeoutUnit = null; + } + + /* Gets the singleton instance of RxWear, after it was initialized. + */ + private static RxWear get() { + if(instance == null) { throw new IllegalStateException("RxWear not initialized"); } + return instance; + } + + + private RxWear(@NonNull Context ctx) { + this.ctx = ctx.getApplicationContext(); + } + + Context getContext() { + return ctx; + } + + static Long getDefaultTimeout() { + return timeoutTime; + } + + static TimeUnit getDefaultTimeoutUnit() { + return timeoutUnit; + } + + + /* Can be used to check whether connection to Wearable API was successful. + * + * This Completable completes if the connection was successful. + */ + public static Completable checkConnection() { + return Completable.fromObservable(Observable.create(new CheckConnectionObservable(RxWear.get()))); + } + + public static Observable getWearableClient() { + return GoogleAPIClientObservable.create(RxWear.get().getContext(), Wearable.API); + } + + public static class Capability { + + private Capability() { } + + // listen + + public static Observable listen(@NonNull String capability) { + return listenInternal(capability, null, null, null, null); + } + + public static Observable listen(@NonNull String capability, long timeout, @NonNull TimeUnit timeUnit) { + return listenInternal(capability, null, null, timeout, timeUnit); + } + + public static Observable listen(@NonNull Uri uri, int filterType) { + return listenInternal(null, uri, filterType, null, null); + } + + public static Observable listen(@NonNull Uri uri, int filterType, long timeout, @NonNull TimeUnit timeUnit) { + return listenInternal(null, uri, filterType, timeout, timeUnit); + } + + private static Observable listenInternal(String capability, Uri uri, Integer filterType, Long timeout, TimeUnit timeUnit) { + return Observable.create(new CapabilityListenerObservable(RxWear.get(), capability, uri, filterType, timeout, timeUnit)); + } + + // getAll + + public static Observable> getAll(int nodeFilter) { + return getAllInternal(nodeFilter, null, null); + } + + public static Observable> getAll(int nodeFilter, long timeout, @NonNull TimeUnit timeUnit) { + return getAllInternal(nodeFilter, timeout, timeUnit); + } + + private static Observable> getAllInternal(int nodeFilter, Long timeout, TimeUnit timeUnit) { + return Observable.create(new CapabilityGetAllObservable(RxWear.get(), nodeFilter, timeout, timeUnit)); + } + + // get + + public static Observable get(@NonNull String capability, int nodeFilter) { + return getInternal(capability, nodeFilter, null, null); + } + + public static Observable get(@NonNull String capability, int nodeFilter, long timeout, @NonNull TimeUnit timeUnit) { + return getInternal(capability, nodeFilter, timeout, timeUnit); + } + + private static Observable getInternal(String capability, int nodeFilter, Long timeout, TimeUnit timeUnit) { + return Observable.create(new CapabilityGetObservable(RxWear.get(), capability, nodeFilter, timeout, timeUnit)); + } + + // addLocal + + public static Observable addLocal(@NonNull String capability) { + return addLocalInternal(capability, null, null); + } + + public static Observable addLocal(@NonNull String capability, long timeout, @NonNull TimeUnit timeUnit) { + return addLocalInternal(capability, timeout, timeUnit); + } + + private static Observable addLocalInternal(String capability, Long timeout, TimeUnit timeUnit) { + return Observable.create(new CapabilityAddLocalObservable(RxWear.get(), capability, timeout, timeUnit)); + } + + // removeLocal + + @SuppressWarnings("ConstantConditions") + public static Observable removeLocal(@NonNull String capability) { + return removeLocalInternal(capability, null, null); + } + + public static Observable removeLocal(@NonNull String capability, long timeout, @NonNull TimeUnit timeUnit) { + return removeLocalInternal(capability, timeout, timeUnit); + } + + private static Observable removeLocalInternal(String capability, Long timeout, TimeUnit timeUnit) { + return Observable.create(new CapabilityRemoveLocalObservable(RxWear.get(), capability, timeout, timeUnit)); + } + } + + + public static class Channel { + + private Channel() { } + + // listen + + public static Observable listen() { + return listenInternal(null, null, null); + } + + public static Observable listen(long timeout, @NonNull TimeUnit timeUnit) { + return listenInternal(null, timeout, timeUnit); + } + + public static Observable listen(@NonNull com.google.android.gms.wearable.Channel channel) { + return listenInternal(channel, null, null); + } + + public static Observable listen(@NonNull com.google.android.gms.wearable.Channel channel, long timeout, @NonNull TimeUnit timeUnit) { + return listenInternal(channel, timeout, timeUnit); + } + + private static Observable listenInternal(com.google.android.gms.wearable.Channel channel, Long timeout, TimeUnit timeUnit) { + return Observable.create(new ChannelListenerObservable(RxWear.get(), channel, timeout, timeUnit)); + } + + // close + + public static Observable close(@NonNull com.google.android.gms.wearable.Channel channel) { + return closeInternal(channel, null, null, null); + } + + public static Observable close(@NonNull com.google.android.gms.wearable.Channel channel, long timeout, @NonNull TimeUnit timeUnit) { + return closeInternal(channel, null, timeout, timeUnit); + } + + public static Observable close(@NonNull com.google.android.gms.wearable.Channel channel, int errorCode) { + return closeInternal(channel, errorCode, null, null); + } + + public static Observable close(@NonNull com.google.android.gms.wearable.Channel channel, int errorCode, long timeout, @NonNull TimeUnit timeUnit) { + return closeInternal(channel, errorCode, timeout, timeUnit); + } + + private static Observable closeInternal(com.google.android.gms.wearable.Channel channel, Integer errorCode, Long timeout, TimeUnit timeUnit) { + return Observable.create(new ChannelCloseObservable(RxWear.get(), channel, errorCode, timeout, timeUnit)); + } + + // sendFile + + public static Observable sendFile(@NonNull com.google.android.gms.wearable.Channel channel, @NonNull Uri uri) { + return sendFileInternal(channel, uri, null, null, null, null); + } + + public static Observable sendFile(@NonNull com.google.android.gms.wearable.Channel channel, @NonNull Uri uri, long timeout, @NonNull TimeUnit timeUnit) { + return sendFileInternal(channel, uri, null, null, timeout, timeUnit); + } + + public static Observable sendFile(@NonNull com.google.android.gms.wearable.Channel channel, @NonNull Uri uri, long startOffset, long length) { + return sendFileInternal(channel, uri, startOffset, length, null, null); + } + + public static Observable sendFile(@NonNull com.google.android.gms.wearable.Channel channel, @NonNull Uri uri, long startOffset, long length, long timeout, @NonNull TimeUnit timeUnit) { + return sendFileInternal(channel, uri, startOffset, length, timeout, timeUnit); + } + + private static Observable sendFileInternal(com.google.android.gms.wearable.Channel channel, Uri uri, Long startOffset, Long length, Long timeout, TimeUnit timeUnit) { + return Observable.create(new ChannelSendFileObservable(RxWear.get(), channel, uri, startOffset, length, timeout, timeUnit)); + } + + // receiveFile + + public static Observable receiveFile(@NonNull com.google.android.gms.wearable.Channel channel, @NonNull Uri uri, boolean append) { + return receiveFileInternal(channel, uri, append, null, null); + } + + public static Observable receiveFile(@NonNull com.google.android.gms.wearable.Channel channel, @NonNull Uri uri, boolean append, long timeout, @NonNull TimeUnit timeUnit) { + return receiveFileInternal(channel, uri, append, timeout, timeUnit); + } + + private static Observable receiveFileInternal(com.google.android.gms.wearable.Channel channel, Uri uri, boolean append, Long timeout, TimeUnit timeUnit) { + return Observable.create(new ChannelReceiveFileObservable(RxWear.get(), channel, uri, append, timeout, timeUnit)); + } + + // getInputStream + + public static Observable getInputStream(@NonNull com.google.android.gms.wearable.Channel channel) { + return getInputStreamInternal(channel, null, null); + } + + public static Observable getInputStream(@NonNull com.google.android.gms.wearable.Channel channel, long timeout, @NonNull TimeUnit timeUnit) { + return getInputStreamInternal(channel, timeout, timeUnit); + } + + private static Observable getInputStreamInternal(com.google.android.gms.wearable.Channel channel, Long timeout, TimeUnit timeUnit) { + return Observable.create(new ChannelGetInputStreamObservable(RxWear.get(), channel, timeout, timeUnit)); + } + + // getOutputStream + + public static Observable getOutputStream(@NonNull com.google.android.gms.wearable.Channel channel) { + return getOutputStreamInternal(channel, null, null); + } + + public static Observable getOutputStream(@NonNull com.google.android.gms.wearable.Channel channel, long timeout, @NonNull TimeUnit timeUnit) { + return getOutputStreamInternal(channel, timeout, timeUnit); + } + + private static Observable getOutputStreamInternal(com.google.android.gms.wearable.Channel channel, Long timeout, TimeUnit timeUnit) { + return Observable.create(new ChannelGetOutputStreamObservable(RxWear.get(), channel, timeout, timeUnit)); + } + + // open + + public static Observable open(@NonNull String nodeId, @NonNull String path) { + return openInternal(nodeId, path, null, null); + } + + public static Observable open(@NonNull String nodeId, @NonNull String path, long timeout, @NonNull TimeUnit timeUnit) { + return openInternal(nodeId, path, timeout, timeUnit); + } + + private static Observable openInternal(String nodeId, String path, Long timeout, TimeUnit timeUnit) { + return Observable.create(new ChannelOpenObservable(RxWear.get(), nodeId, path, timeout, timeUnit)); + } + } + + public static class Data { + + private Data() { } + + // listen + + public static Observable listen() { + return listenInternal(null, null, null, null); + } + + public static Observable listen(long timeout, @NonNull TimeUnit timeUnit) { + return listenInternal(null, null, timeout, timeUnit); + } + + public static Observable listen(@NonNull Uri uri, int filterType) { + return listenInternal(uri, filterType, null, null); + } + + public static Observable listen(@NonNull Uri uri, int filterType, long timeout, @NonNull TimeUnit timeUnit) { + return listenInternal(uri, filterType, timeout, timeUnit); + } + + private static Observable listenInternal(Uri uri, Integer filterType, Long timeout, TimeUnit timeUnit) { + return Observable.create(new DataListenerObservable(RxWear.get(), uri, filterType, timeout, timeUnit)); + } + + // delete + + public static Observable delete(@NonNull Uri uri) { + return deleteInternal(uri, null, null, null); + } + + public static Observable delete(@NonNull Uri uri, @NonNull Long timeout, @NonNull TimeUnit timeUnit) { + return deleteInternal(uri, null, timeout, timeUnit); + } + + public static Observable delete(@NonNull Uri uri, int filterType) { + return deleteInternal(uri, filterType, null, null); + } + + public static Observable delete(@NonNull Uri uri, int filterType, long timeout, @NonNull TimeUnit timeUnit) { + return deleteInternal(uri, filterType, timeout, timeUnit); + } + + private static Observable deleteInternal(Uri uri, Integer filterType, Long timeout, TimeUnit timeUnit) { + return Observable.create(new DataDeleteItemsObservable(RxWear.get(), uri, filterType, timeout, timeUnit)); + } + + // put + + public static Observable put(@NonNull PutDataRequest putDataRequest) { + return putInternal(putDataRequest, null, null); + } + + public static Observable put(@NonNull PutDataRequest putDataRequest, long timeout, @NonNull TimeUnit timeUnit) { + return putInternal(putDataRequest, timeout, timeUnit); + } + + public static Observable put(@NonNull PutDataMapRequest putDataMapRequest) { + return putInternal(putDataMapRequest.asPutDataRequest(), null, null); + } + + public static Observable put(@NonNull PutDataMapRequest putDataMapRequest, long timeout, @NonNull TimeUnit timeUnit) { + return putInternal(putDataMapRequest.asPutDataRequest(), timeout, timeUnit); + } + + private static Observable putInternal(PutDataRequest putDataRequest, Long timeout, TimeUnit timeUnit) { + return Observable.create(new DataPutItemObservable(RxWear.get(), putDataRequest, timeout, timeUnit)); + } + + + // getSingle + + public static Observable getSingle(@NonNull Uri uri) { + return getSingleInternal(uri, null, null); + } + + public static Observable getSingle(@NonNull Uri uri, long timeout, @NonNull TimeUnit timeUnit) { + return getSingleInternal(uri, timeout, timeUnit); + } + + private static Observable getSingleInternal(Uri uri, Long timeout, TimeUnit timeUnit) { + return Observable.create(new DataGetItemObservable(RxWear.get(), uri, timeout, timeUnit)); + } + + // get + + public static Observable get(@NonNull Uri uri, int filterType) { + return getInternal(uri, filterType, null, null); + } + + public static Observable get(@NonNull Uri uri, int filterType, long timeout, @NonNull TimeUnit timeUnit) { + return getInternal(uri, filterType, timeout, timeUnit); + } + + public static Observable get(@NonNull Uri uri) { + return getInternal(uri, null, null, null); + } + + public static Observable get(@NonNull Uri uri, long timeout, @NonNull TimeUnit timeUnit) { + return getInternal(uri, null, timeout, timeUnit); + } + + public static Observable get() { + return getInternal(null, null, null, null); + } + + public static Observable get(long timeout, @NonNull TimeUnit timeUnit) { + return getInternal(null, null, timeout, timeUnit); + } + + private static Observable getInternal(Uri uri, Integer filterType, Long timeout, TimeUnit timeUnit) { + return Observable.create(new DataGetItemsObservable(RxWear.get(), uri, filterType, timeout, timeUnit)); + } + + // getFdForAsset + + public static Observable getFdForAsset(@NonNull DataItemAsset dataItemAsset) { + return getFdForAssetInternal(dataItemAsset, null, null, null); + } + + public static Observable getFdForAsset(@NonNull DataItemAsset dataItemAsset, long timeout, @NonNull TimeUnit timeUnit) { + return getFdForAssetInternal(dataItemAsset, null, timeout, timeUnit); + } + + public static Observable getFdForAsset(@NonNull Asset asset) { + return getFdForAssetInternal(null, asset, null, null); + } + + public static Observable getFdForAsset(@NonNull Asset asset, long timeout, @NonNull TimeUnit timeUnit) { + return getFdForAssetInternal(null, asset, timeout, timeUnit); + } + + private static Observable getFdForAssetInternal(DataItemAsset dataItemAsset, Asset asset, Long timeout, TimeUnit timeUnit) { + return Observable.create(new DataGetFdForAssetObservable(RxWear.get(), dataItemAsset, asset, timeout, timeUnit)); + } + + /* A helper class with a fluent interface for putting a Serializable + * based on a DataMap. + * + * Example: + * RxWear.Data.PutSerializable.to("/path", serializable) + * .setUrgent() + * .toObservable() + * .subscribe(dataItem -> { + * // do something + * }); + */ + public static class PutSerializable { + + private PutSerializable() { } + + public static Observable withDataItem(DataItem dataItem, Serializable serializable) { + return createObservable(PutDataRequest.createFromDataItem(dataItem), serializable); + } + + public static Observable urgentWithDataItem(DataItem dataItem, Serializable serializable) { + return createObservable(PutDataRequest.createFromDataItem(dataItem).setUrgent(), serializable); + } + + public static Observable withAutoAppendedId(String pathPrefix, Serializable serializable) { + return createObservable(PutDataRequest.createWithAutoAppendedId(pathPrefix), serializable); + } + + public static Observable urgentWithAutoAppendedId(String pathPrefix, Serializable serializable) { + return createObservable(PutDataRequest.createWithAutoAppendedId(pathPrefix).setUrgent(), serializable); + } + + public static Observable to(String path, Serializable serializable) { + return createObservable(PutDataRequest.create(path), serializable); + } + + public static Observable urgentTo(String path, Serializable serializable) { + return createObservable(PutDataRequest.create(path).setUrgent(), serializable); + } + + private static Observable createObservable(PutDataRequest request, Serializable serializable) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + new ObjectOutputStream(out).writeObject(serializable); + request.setData(out.toByteArray()); + return putInternal(request, null, null); + } catch(IOException e) { + return Observable.error(e); + } + } + + } + + /* A helper class with a fluent interface for putting a DataItem + * based on a DataMap. + * + * Example: + * RxWear.Data.PutDataMap.to("/path") + * .putString("key", "value") + * .putInt("key", 0) + * .toObservable() + * .subscribe(dataItem -> { + * // do something + * }); + */ + public static class PutDataMap { + private final PutDataMapRequest request; + + private PutDataMap(String path, DataMapItem dataMapItem, String pathPrefix) { + if(path != null) { + request = PutDataMapRequest.create(path); + } else if(dataMapItem != null) { + request = PutDataMapRequest.createFromDataMapItem(dataMapItem); + } else { + request = PutDataMapRequest.createWithAutoAppendedId(pathPrefix); + } + } + + public static PutDataMap withDataMapItem(DataMapItem source) { + return new PutDataMap(null, source, null); + } + + public static PutDataMap withAutoAppendedId(String pathPrefix) { + return new PutDataMap(null, null, pathPrefix); + } + + public static PutDataMap to(String path) { + return new PutDataMap(path, null, null); + } + + public PutDataMap setUrgent() { + request.setUrgent(); + return this; + } + + public PutDataMap putAll(DataMap dataMap) { + request.getDataMap().putAll(dataMap); + return this; + } + + public PutDataMap putBoolean(String key, boolean value) { + request.getDataMap().putBoolean(key, value); + return this; + } + + public PutDataMap putByte(String key, byte value) { + request.getDataMap().putByte(key, value); + return this; + } + + public PutDataMap putInt(String key, int value) { + request.getDataMap().putInt(key, value); + return this; + } + + public PutDataMap putLong(String key, long value) { + request.getDataMap().putLong(key, value); + return this; + } + + public PutDataMap putFloat(String key, float value) { + request.getDataMap().putFloat(key, value); + return this; + } + + public PutDataMap putDouble(String key, double value) { + request.getDataMap().putDouble(key, value); + return this; + } + + public PutDataMap putString(String key, String value) { + request.getDataMap().putString(key, value); + return this; + } + + public PutDataMap putAsset(String key, Asset value) { + request.getDataMap().putAsset(key, value); + return this; + } + + public PutDataMap putDataMap(String key, DataMap value) { + request.getDataMap().putDataMap(key, value); + return this; + } + + public PutDataMap putDataMapArrayList(String key, ArrayList value) { + request.getDataMap().putDataMapArrayList(key, value); + return this; + } + + public PutDataMap putIntegerArrayList(String key, ArrayList value) { + request.getDataMap().putIntegerArrayList(key, value); + return this; + } + + public PutDataMap putStringArrayList(String key, ArrayList value) { + request.getDataMap().putStringArrayList(key, value); + return this; + } + + public PutDataMap putByteArray(String key, byte[] value) { + request.getDataMap().putByteArray(key, value); + return this; + } + + public PutDataMap putLongArray(String key, long[] value) { + request.getDataMap().putLongArray(key, value); + return this; + } + + public PutDataMap putFloatArray(String key, float[] value) { + request.getDataMap().putFloatArray(key, value); + return this; + } + + public PutDataMap putStringArray(String key, String[] value) { + request.getDataMap().putStringArray(key, value); + return this; + } + + public Observable toObservable() { + return putInternal(request.asPutDataRequest(), null, null); + } + } + + } + + public static class Message { + + private Message() { } + + // listen + + public static Observable listen() { + return listenInternal(null, null, null, null); + } + + public static Observable listen(@NonNull Long timeout, @NonNull TimeUnit timeUnit) { + return listenInternal(null, null, timeout, timeUnit); + } + + public static Observable listen(@NonNull Uri uri, int filterType) { + return listenInternal(uri, filterType, null, null); + } + + public static Observable listen(@NonNull Uri uri, int filterType, long timeout, @NonNull TimeUnit timeUnit) { + return listenInternal(uri, filterType, timeout, timeUnit); + } + + private static Observable listenInternal(Uri uri, Integer filterType, Long timeout, TimeUnit timeUnit) { + return Observable.create(new MessageListenerObservable(RxWear.get(), uri, filterType, timeout, timeUnit)); + } + + // send + + public static Observable send(@NonNull String nodeId, @NonNull String path, @NonNull byte[] data) { + return sendInternal(nodeId, path, data, null, null); + } + + public static Observable send(@NonNull String nodeId, @NonNull String path, @NonNull byte[] data, long timeout, @NonNull TimeUnit timeUnit) { + return sendInternal(nodeId, path, data, timeout, timeUnit); + } + + private static Observable sendInternal(String nodeId, String path, byte[] data, Long timeout, TimeUnit timeUnit) { + return Observable.create(new MessageSendObservable(RxWear.get(), nodeId, path, data, timeout, timeUnit)); + } + + // sendToAllRemoteNodes + + public static Observable sendToAllRemoteNodes(@NonNull final String path, @NonNull final byte[] data) { + return sendToAllRemoteNodesInternal(path, data, null, null); + } + + public static Observable sendToAllRemoteNodes(@NonNull final String path, @NonNull final byte[] data, final long timeout, @NonNull final TimeUnit timeUnit) { + return sendToAllRemoteNodesInternal(path, data, timeout, timeUnit); + } + + private static Observable sendToAllRemoteNodesInternal(final String path, final byte[] data, final Long timeout, final TimeUnit timeUnit) { + return Node.getRemoteNodesInternal(timeout, timeUnit).flatMap(new Func1, Observable>() { + @Override + public Observable call(List nodes) { + return Observable.from(nodes); + } + }).flatMap(new Func1>() { + @Override + public Observable call(com.google.android.gms.wearable.Node node) { + return sendInternal(node.getId(), path, data, timeout, timeUnit); + } + }); + } + + /* A helper class to send a Serializable in a message */ + public static class SendSerializable { + + private SendSerializable() { } + + public static Observable to(String nodeId, String path, Serializable serializable) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + new ObjectOutputStream(out).writeObject(serializable); + return sendInternal(nodeId, path, out.toByteArray(), null, null); + } catch(Throwable throwable) { + return Observable.error(throwable); + } + } + + public static Observable toAllRemoteNodes(String path, Serializable serializable) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + new ObjectOutputStream(out).writeObject(serializable); + return sendToAllRemoteNodesInternal(path, out.toByteArray(), null, null); + } catch(Throwable throwable) { + return Observable.error(throwable); + } + } + } + + /* A helper class with a fluent interface for putting a DataItem + * based on a DataMap. + * + * Example: + * RxWear.Message.SendDataMap.to(nodeId, "/path") + * .putString("key", "value") + * .putInt("key", 0) + * .toObservable() + * .subscribe(requestId -> { + * // do something + * }); + */ + public static class SendDataMap { + private final String nodeId; + private final String path; + private final DataMap dataMap = new DataMap(); + private final boolean toAllRemoteNodes; + + private SendDataMap(String nodeId, String path, boolean toAllRemoteNodes) { + this.nodeId = nodeId; + this.path = path; + this.toAllRemoteNodes = toAllRemoteNodes; + } + + public static SendDataMap to(String nodeId, String path) { + return new SendDataMap(nodeId, path, false); + } + + public static SendDataMap toAllRemoteNodes(String path) { + return new SendDataMap(null, path, true); + } + + public SendDataMap putAll(DataMap dataMap) { + dataMap.putAll(dataMap); + return this; + } + + public SendDataMap putBoolean(String key, boolean value) { + dataMap.putBoolean(key, value); + return this; + } + + public SendDataMap putByte(String key, byte value) { + dataMap.putByte(key, value); + return this; + } + + public SendDataMap putInt(String key, int value) { + dataMap.putInt(key, value); + return this; + } + + public SendDataMap putLong(String key, long value) { + dataMap.putLong(key, value); + return this; + } + + public SendDataMap putFloat(String key, float value) { + dataMap.putFloat(key, value); + return this; + } + + public SendDataMap putDouble(String key, double value) { + dataMap.putDouble(key, value); + return this; + } + + public SendDataMap putString(String key, String value) { + dataMap.putString(key, value); + return this; + } + + public SendDataMap putAsset(String key, Asset value) { + dataMap.putAsset(key, value); + return this; + } + + public SendDataMap putDataMap(String key, DataMap value) { + dataMap.putDataMap(key, value); + return this; + } + + public SendDataMap putDataMapArrayList(String key, ArrayList value) { + dataMap.putDataMapArrayList(key, value); + return this; + } + + public SendDataMap putIntegerArrayList(String key, ArrayList value) { + dataMap.putIntegerArrayList(key, value); + return this; + } + + public SendDataMap putStringArrayList(String key, ArrayList value) { + dataMap.putStringArrayList(key, value); + return this; + } + + public SendDataMap putByteArray(String key, byte[] value) { + dataMap.putByteArray(key, value); + return this; + } + + public SendDataMap putLongArray(String key, long[] value) { + dataMap.putLongArray(key, value); + return this; + } + + public SendDataMap putFloatArray(String key, float[] value) { + dataMap.putFloatArray(key, value); + return this; + } + + public SendDataMap putStringArray(String key, String[] value) { + dataMap.putStringArray(key, value); + return this; + } + + public Observable toObservable() { + if(toAllRemoteNodes) { + return Message.sendToAllRemoteNodesInternal(path, dataMap.toByteArray(), null, null); + } else { + return Message.sendInternal(nodeId, path, dataMap.toByteArray(), null, null); + } + } + } + + } + + + public static class Node { + + private Node() { } + + // listen + + @Deprecated + public static Observable listen() { + return listenInternal(null, null); + } + + @Deprecated + public static Observable listen(long timeout, @NonNull TimeUnit timeUnit) { + return listenInternal(timeout, timeUnit); + } + + private static Observable listenInternal(Long timeout, TimeUnit timeUnit) { + return Observable.create(new NodeListenerObservable(RxWear.get(), timeout, timeUnit)); + } + + // getConnectedNodes + + public static Observable> getConnectedNodes() { + return getConnectedNodesInternal(null, null); + } + + public static Observable> getConnectedNodes(long timeout, @NonNull TimeUnit timeUnit) { + return getConnectedNodesInternal(timeout, timeUnit); + } + + private static Observable> getConnectedNodesInternal(Long timeout, TimeUnit timeUnit) { + return Observable.create(new NodeGetConnectedObservable(RxWear.get(), timeout, timeUnit)); + } + + // getLocalNode + + public static Observable getLocalNode() { + return getLocalNodeInternal(null, null); + } + + public static Observable getLocalNode(long timeout, @NonNull TimeUnit timeUnit) { + return getLocalNodeInternal(timeout, timeUnit); + } + + private static Observable getLocalNodeInternal(Long timeout, TimeUnit timeUnit) { + return Observable.create(new NodeGetLocalObservable(RxWear.get(), timeout, timeUnit)); + } + + // getRemoteNodes + + public static Observable> getRemoteNodes() { + return getRemoteNodesInternal(null, null); + } + + public static Observable> getRemoteNodes(long timeout, @NonNull TimeUnit timeUnit) { + return getRemoteNodesInternal(timeout, timeUnit); + } + + private static Observable> getRemoteNodesInternal(Long timeout, TimeUnit timeUnit) { + return getConnectedNodesInternal(timeout, timeUnit).zipWith(getLocalNodeInternal(timeout, timeUnit), new Func2, com.google.android.gms.wearable.Node, List>() { + @Override + public List call(List connectedNodes, com.google.android.gms.wearable.Node localNode) { + Iterator iterator = connectedNodes.iterator(); + + while(iterator.hasNext()) { + com.google.android.gms.wearable.Node node = iterator.next(); + if(node.getId().equals(localNode.getId())) { iterator.remove(); } + } + + return connectedNodes; + } + }); + } + + } +} diff --git a/library/src/main/java/com/patloew/rxwear/StatusException.java b/library/src/main/java/com/patloew/rxwear/StatusException.java new file mode 100644 index 0000000..e6216b9 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/StatusException.java @@ -0,0 +1,29 @@ +package com.patloew.rxwear; + +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/rxwear/StatusResultCallBack.java b/library/src/main/java/com/patloew/rxwear/StatusResultCallBack.java new file mode 100644 index 0000000..2ba4c0f --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/StatusResultCallBack.java @@ -0,0 +1,40 @@ +package com.patloew.rxwear; + +import android.support.annotation.NonNull; + +import com.google.android.gms.common.api.ResultCallback; +import com.google.android.gms.common.api.Status; + +import rx.Observer; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class 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/java/com/patloew/rxwear/events/ChannelClosedEvent.java b/library/src/main/java/com/patloew/rxwear/events/ChannelClosedEvent.java new file mode 100644 index 0000000..bd56f91 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/events/ChannelClosedEvent.java @@ -0,0 +1,28 @@ +package com.patloew.rxwear.events; + +import com.google.android.gms.wearable.Channel; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class ChannelClosedEvent extends ChannelEvent{ + + public final int closeReason; + public final int appSpecificErrorCode; + + public ChannelClosedEvent(Channel channel, int closeReason, int appSpecificErrorCode) { + super(channel); + this.closeReason = closeReason; + this.appSpecificErrorCode = appSpecificErrorCode; + } +} diff --git a/library/src/main/java/com/patloew/rxwear/events/ChannelEvent.java b/library/src/main/java/com/patloew/rxwear/events/ChannelEvent.java new file mode 100644 index 0000000..59775e6 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/events/ChannelEvent.java @@ -0,0 +1,25 @@ +package com.patloew.rxwear.events; + +import com.google.android.gms.wearable.Channel; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public abstract class ChannelEvent { + + public final Channel channel; + + public ChannelEvent(Channel channel) { + this.channel = channel; + } +} diff --git a/library/src/main/java/com/patloew/rxwear/events/ChannelOpenedEvent.java b/library/src/main/java/com/patloew/rxwear/events/ChannelOpenedEvent.java new file mode 100644 index 0000000..dfeb5c7 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/events/ChannelOpenedEvent.java @@ -0,0 +1,22 @@ +package com.patloew.rxwear.events; + +import com.google.android.gms.wearable.Channel; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class ChannelOpenedEvent extends ChannelEvent{ + public ChannelOpenedEvent(Channel channel) { + super(channel); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/events/InputClosedEvent.java b/library/src/main/java/com/patloew/rxwear/events/InputClosedEvent.java new file mode 100644 index 0000000..d7b2c8e --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/events/InputClosedEvent.java @@ -0,0 +1,28 @@ +package com.patloew.rxwear.events; + +import com.google.android.gms.wearable.Channel; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class InputClosedEvent extends ChannelEvent{ + + public final int closeReason; + public final int appSpecificErrorCode; + + public InputClosedEvent(Channel channel, int closeReason, int appSpecificErrorCode) { + super(channel); + this.closeReason = closeReason; + this.appSpecificErrorCode = appSpecificErrorCode; + } +} diff --git a/library/src/main/java/com/patloew/rxwear/events/NodeEvent.java b/library/src/main/java/com/patloew/rxwear/events/NodeEvent.java new file mode 100644 index 0000000..044ad62 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/events/NodeEvent.java @@ -0,0 +1,26 @@ +package com.patloew.rxwear.events; + +import com.google.android.gms.wearable.Node; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class NodeEvent { + public final boolean connected; + public final Node peer; + + public NodeEvent(Node peer, boolean connected) { + this.connected = connected; + this.peer = peer; + } +} diff --git a/library/src/main/java/com/patloew/rxwear/events/OutputClosedEvent.java b/library/src/main/java/com/patloew/rxwear/events/OutputClosedEvent.java new file mode 100644 index 0000000..628814b --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/events/OutputClosedEvent.java @@ -0,0 +1,28 @@ +package com.patloew.rxwear.events; + +import com.google.android.gms.wearable.Channel; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ +public class OutputClosedEvent extends ChannelEvent{ + + public final int closeReason; + public final int appSpecificErrorCode; + + public OutputClosedEvent(Channel channel, int closeReason, int appSpecificErrorCode) { + super(channel); + this.closeReason = closeReason; + this.appSpecificErrorCode = appSpecificErrorCode; + } +} diff --git a/library/src/main/java/com/patloew/rxwear/transformers/DataEventGetDataMap.java b/library/src/main/java/com/patloew/rxwear/transformers/DataEventGetDataMap.java new file mode 100644 index 0000000..2be9839 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/transformers/DataEventGetDataMap.java @@ -0,0 +1,95 @@ +package com.patloew.rxwear.transformers; + +import com.google.android.gms.wearable.DataEvent; +import com.google.android.gms.wearable.DataMap; + +import rx.Observable; +import rx.functions.Func1; + +/* 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. + * + * --------------------------------- + * + * Transformer that optionally filters DataEvents by path and/or type and + * returns an Observable of the dataMap from the DataItem. + */ +public class DataEventGetDataMap implements Observable.Transformer { + + private final String path; + private final boolean isPrefix; + private final Integer type; + + private DataEventGetDataMap(String path, boolean isPrefix, Integer type) { + this.path = path; + this.isPrefix = isPrefix; + this.type = type; + } + + public static Observable.Transformer noFilter() { + return new DataEventGetDataMap(null, false, null); + } + + public static Observable.Transformer filterByPath(String path) { + return new DataEventGetDataMap(path, false, null); + } + + public static Observable.Transformer filterByPathAndType(String path, int type) { + return new DataEventGetDataMap(path, false, type); + } + + public static Observable.Transformer filterByPathPrefix(String pathPrefix) { + return new DataEventGetDataMap(pathPrefix, true, null); + } + + public static Observable.Transformer filterByPathPrefixAndType(String pathPrefix, int type) { + return new DataEventGetDataMap(pathPrefix, true, type); + } + + public static Observable.Transformer filterByType(int type) { + return new DataEventGetDataMap(null, false, type); + } + + @Override + public Observable call(Observable observable) { + if(type != null) { + observable = observable.filter(new Func1() { + @Override + public Boolean call(DataEvent dataEvent) { + return dataEvent.getType() == type; + } + }); + } + + if(path != null) { + observable = observable.filter(new Func1() { + @Override + public Boolean call(DataEvent dataEvent) { + if (isPrefix) { + return dataEvent.getDataItem().getUri().getPath().startsWith(path); + } else { + return dataEvent.getDataItem().getUri().getPath().equals(path); + } + } + }); + } + + return observable.map(new Func1() { + @Override + public DataMap call(DataEvent dataEvent) { + return DataMap.fromByteArray(dataEvent.getDataItem().getData()); + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/transformers/DataEventGetSerializable.java b/library/src/main/java/com/patloew/rxwear/transformers/DataEventGetSerializable.java new file mode 100644 index 0000000..61229f6 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/transformers/DataEventGetSerializable.java @@ -0,0 +1,109 @@ +package com.patloew.rxwear.transformers; + +import com.google.android.gms.wearable.DataEvent; + +import java.io.ByteArrayInputStream; +import java.io.ObjectInputStream; +import java.io.Serializable; + +import rx.Observable; +import rx.exceptions.Exceptions; +import rx.functions.Func1; + +/* 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. + * + * --------------------------------- + * + * Transformer that optionally filters DataEvents by path and/or type and + * returns an Observable of the Serializable from the DataItem. + * + * Example: DataEventGetSerializable.filterByPathAndType("/path", DataEvent.TYPE_CHANGED) + */ +public class DataEventGetSerializable implements Observable.Transformer { + + private final String path; + private final boolean isPrefix; + private final Integer type; + + private DataEventGetSerializable(String path, boolean isPrefix, Integer type) { + this.path = path; + this.isPrefix = isPrefix; + this.type = type; + + } + + public static Observable.Transformer noFilter() { + return new DataEventGetSerializable(null, false, null); + } + + public static Observable.Transformer filterByPath(String path) { + return new DataEventGetSerializable(path, false, null); + } + + public static Observable.Transformer filterByPathAndType(String path, int type) { + return new DataEventGetSerializable(path, false, type); + } + + public static Observable.Transformer filterByPathPrefix(String pathPrefix) { + return new DataEventGetSerializable(pathPrefix, true, null); + } + + public static Observable.Transformer filterByPathPrefixAndType(String pathPrefix, int type) { + return new DataEventGetSerializable(pathPrefix, true, type); + } + + public static Observable.Transformer filterByType(int type) { + return new DataEventGetSerializable(null, false, type); + } + + @Override + public Observable call(Observable observable) { + if(type != null) { + observable = observable.filter(new Func1() { + @Override + public Boolean call(DataEvent dataEvent) { + return dataEvent.getType() == type; + } + }); + } + + if(path != null) { + observable = observable.filter(new Func1() { + @Override + public Boolean call(DataEvent dataEvent) { + if (isPrefix) { + return dataEvent.getDataItem().getUri().getPath().startsWith(path); + } else { + return dataEvent.getDataItem().getUri().getPath().equals(path); + } + } + }); + } + + return observable.map(new Func1() { + @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) + @Override + public T call(DataEvent dataEvent) { + try { + ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(dataEvent.getDataItem().getData())); + return (T) objectInputStream.readObject(); + } catch(Exception e) { + Exceptions.propagate(e); + return null; + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/transformers/DataItemGetDataMap.java b/library/src/main/java/com/patloew/rxwear/transformers/DataItemGetDataMap.java new file mode 100644 index 0000000..67f99f8 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/transformers/DataItemGetDataMap.java @@ -0,0 +1,72 @@ +package com.patloew.rxwear.transformers; + +import com.google.android.gms.wearable.DataItem; +import com.google.android.gms.wearable.DataMap; + +import rx.Observable; +import rx.functions.Func1; + +/* 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. + * + * --------------------------------- + * + * Transformer that optionally filters DataItems by path and/or type and + * returns an Observable of the dataMap from the DataItem. + */ +public class DataItemGetDataMap implements Observable.Transformer { + + private final String path; + private final boolean isPrefix; + + private DataItemGetDataMap(String path, boolean isPrefix) { + this.path = path; + this.isPrefix = isPrefix; + } + + public static Observable.Transformer noFilter() { + return new DataItemGetDataMap(null, false); + } + + public static Observable.Transformer filterByPath(String path) { + return new DataItemGetDataMap(path, false); + } + + public static Observable.Transformer filterByPathPrefix(String pathPrefix) { + return new DataItemGetDataMap(pathPrefix, true); + } + + @Override + public Observable call(Observable observable) { + if(path != null) { + observable = observable.filter(new Func1() { + @Override + public Boolean call(DataItem dataItem) { + if (isPrefix) { + return dataItem.getUri().getPath().startsWith(path); + } else { + return dataItem.getUri().getPath().equals(path); + } + } + }); + } + + return observable.map(new Func1() { + @Override + public DataMap call(DataItem dataItem) { + return DataMap.fromByteArray(dataItem.getData()); + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/transformers/DataItemGetSerializable.java b/library/src/main/java/com/patloew/rxwear/transformers/DataItemGetSerializable.java new file mode 100644 index 0000000..4e0169e --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/transformers/DataItemGetSerializable.java @@ -0,0 +1,84 @@ +package com.patloew.rxwear.transformers; + +import com.google.android.gms.wearable.DataItem; + +import java.io.ByteArrayInputStream; +import java.io.ObjectInputStream; +import java.io.Serializable; + +import rx.Observable; +import rx.exceptions.Exceptions; +import rx.functions.Func1; + +/* 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. + * + * --------------------------------- + * + * Transformer that optionally filters DataEvents by path and/or type and + * returns an Observable of the Serializable from the DataItem. + * + * Example: DataItemGetSerializable.filterByPath("/path") + */ +public class DataItemGetSerializable implements Observable.Transformer { + + private final String path; + private final boolean isPrefix; + + private DataItemGetSerializable(String path, boolean isPrefix) { + this.path = path; + this.isPrefix = isPrefix; + } + + public static Observable.Transformer noFilter() { + return new DataItemGetSerializable(null, false); + } + + public static Observable.Transformer filterByPath(String path) { + return new DataItemGetSerializable(path, false); + } + + public static Observable.Transformer filterByPathPrefix(String pathPrefix) { + return new DataItemGetSerializable(pathPrefix, true); + } + @Override + public Observable call(Observable observable) { + if(path != null) { + observable = observable.filter(new Func1() { + @Override + public Boolean call(DataItem dataItem) { + if (isPrefix) { + return dataItem.getUri().getPath().startsWith(path); + } else { + return dataItem.getUri().getPath().equals(path); + } + } + }); + } + + return observable.map(new Func1() { + @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) + @Override + public T call(DataItem dataItem) { + try { + ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(dataItem.getData())); + return (T) objectInputStream.readObject(); + } catch(Exception e) { + Exceptions.propagate(e); + return null; + } + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/transformers/MessageEventGetDataMap.java b/library/src/main/java/com/patloew/rxwear/transformers/MessageEventGetDataMap.java new file mode 100644 index 0000000..1eff7f5 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/transformers/MessageEventGetDataMap.java @@ -0,0 +1,72 @@ +package com.patloew.rxwear.transformers; + +import com.google.android.gms.wearable.DataMap; +import com.google.android.gms.wearable.MessageEvent; + +import rx.Observable; +import rx.functions.Func1; + +/* 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. + * + * --------------------------------- + * + * Transformer that filters MessageEvents by path and returns an + * Observable of the dataMap from the MessageEvent. + */ +public class MessageEventGetDataMap implements Observable.Transformer { + + private final String path; + private final boolean isPrefix; + + private MessageEventGetDataMap(String path, boolean isPrefix) { + this.path = path; + this.isPrefix = isPrefix; + } + + public static Observable.Transformer noFilter() { + return new MessageEventGetDataMap(null, false); + } + + public static Observable.Transformer filterByPath(String path) { + return new MessageEventGetDataMap(path, false); + } + + public static Observable.Transformer filterByPathPrefix(String pathPrefix) { + return new MessageEventGetDataMap(pathPrefix, true); + } + + @Override + public Observable call(Observable observable) { + if(path != null) { + observable = observable.filter(new Func1() { + @Override + public Boolean call(MessageEvent messageEvent) { + if (isPrefix) { + return messageEvent.getPath().startsWith(path); + } else { + return messageEvent.getPath().equals(path); + } + } + }); + } + + return observable.map(new Func1() { + @Override + public DataMap call(MessageEvent messageEvent) { + return DataMap.fromByteArray(messageEvent.getData()); + } + }); + } +} diff --git a/library/src/main/java/com/patloew/rxwear/transformers/MessageEventGetSerializable.java b/library/src/main/java/com/patloew/rxwear/transformers/MessageEventGetSerializable.java new file mode 100644 index 0000000..f89da97 --- /dev/null +++ b/library/src/main/java/com/patloew/rxwear/transformers/MessageEventGetSerializable.java @@ -0,0 +1,85 @@ +package com.patloew.rxwear.transformers; + +import com.google.android.gms.wearable.MessageEvent; + +import java.io.ByteArrayInputStream; +import java.io.ObjectInputStream; +import java.io.Serializable; + +import rx.Observable; +import rx.exceptions.Exceptions; +import rx.functions.Func1; + +/* 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. + * + * --------------------------------- + * + * Transformer that optionally filters MessageEvents by path and returns an + * Observable of the Serializable from the MessageEvent. + * + * Example: MessageEventGetSerializable.filterByPath("/path") + */ +public class MessageEventGetSerializable implements Observable.Transformer { + + private final String path; + private final boolean isPrefix; + + private MessageEventGetSerializable(String path, boolean isPrefix) { + this.path = path; + this.isPrefix = isPrefix; + } + + public static Observable.Transformer noFilter() { + return new MessageEventGetSerializable(null, false); + } + + public static Observable.Transformer filterByPath(String path) { + return new MessageEventGetSerializable(path, false); + } + + public static Observable.Transformer filterByPathPrefix(String pathPrefix) { + return new MessageEventGetSerializable(pathPrefix, true); + } + + @Override + public Observable call(Observable observable) { + if(path != null) { + observable = observable.filter(new Func1() { + @Override + public Boolean call(MessageEvent messageEvent) { + if (isPrefix) { + return messageEvent.getPath().startsWith(path); + } else { + return messageEvent.getPath().equals(path); + } + } + }); + } + + return observable.map(new Func1() { + @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) + @Override + public T call(MessageEvent messageEvent) { + try { + ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(messageEvent.getData())); + return (T) objectInputStream.readObject(); + } catch(Exception e) { + Exceptions.propagate(e); + return null; + } + } + }); + } +} \ No newline at end of file diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml new file mode 100644 index 0000000..0ff9f00 --- /dev/null +++ b/library/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + RxWear + diff --git a/library/src/test/java/com/patloew/rxwear/RxWearTest.java b/library/src/test/java/com/patloew/rxwear/RxWearTest.java new file mode 100644 index 0000000..786ca44 --- /dev/null +++ b/library/src/test/java/com/patloew/rxwear/RxWearTest.java @@ -0,0 +1,1457 @@ +package com.patloew.rxwear; + +import android.content.Context; +import android.net.Uri; +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.wearable.Asset; +import com.google.android.gms.wearable.CapabilityApi; +import com.google.android.gms.wearable.CapabilityInfo; +import com.google.android.gms.wearable.Channel; +import com.google.android.gms.wearable.ChannelApi; +import com.google.android.gms.wearable.DataApi; +import com.google.android.gms.wearable.DataEvent; +import com.google.android.gms.wearable.DataItem; +import com.google.android.gms.wearable.DataItemAsset; +import com.google.android.gms.wearable.DataItemBuffer; +import com.google.android.gms.wearable.MessageApi; +import com.google.android.gms.wearable.MessageEvent; +import com.google.android.gms.wearable.Node; +import com.google.android.gms.wearable.NodeApi; +import com.google.android.gms.wearable.PutDataRequest; +import com.google.android.gms.wearable.Wearable; +import com.patloew.rxwear.events.ChannelEvent; +import com.patloew.rxwear.events.NodeEvent; + +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.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import rx.Completable; +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; + +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +/* Copyright 2016 Patrick Löwenstein + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ + +@RunWith(PowerMockRunner.class) +@PrepareOnlyThisForTest({ ContextCompat.class, Wearable.class, Status.class, ConnectionResult.class }) +@SuppressStaticInitializationFor("com.google.android.gms.wearable.Wearable") +public class RxWearTest { + + @Mock Context ctx; + + @Mock GoogleApiClient apiClient; + @Mock Status status; + @Mock ConnectionResult connectionResult; + @Mock PendingResult pendingResult; + + @Mock CapabilityInfo capabilityInfo; + @Mock Uri uri; + @Mock Channel channel; + @Mock DataItem dataItem; + @Mock DataItemBuffer dataItemBuffer; + + @Mock CapabilityApi capabilityApi; + @Mock ChannelApi channelApi; + @Mock DataApi dataApi; + @Mock MessageApi messageApi; + @Mock NodeApi nodeApi; + + @Mock RxWear rxWear; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + PowerMockito.mockStatic(Wearable.class); + Whitebox.setInternalState(Wearable.class, capabilityApi); + Whitebox.setInternalState(Wearable.class, channelApi); + Whitebox.setInternalState(Wearable.class, dataApi); + Whitebox.setInternalState(Wearable.class, messageApi); + Whitebox.setInternalState(Wearable.class, nodeApi); + + when(ctx.getApplicationContext()).thenReturn(ctx); + } + + ////////////////// + // UTIL METHODS // + ////////////////// + + + // Mock GoogleApiClient connection success behaviour + private void setupBaseObservableSuccess(final BaseObservable baseObservable) { + setupBaseObservableSuccess(baseObservable, apiClient); + } + + // Mock GoogleApiClient connection success behaviour + private void setupBaseObservableSuccess(final BaseObservable baseObservable, final GoogleApiClient apiClient) { + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + final Subscriber subscriber = (Subscriber) invocation.getArguments()[0]; + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + baseObservable.onGoogleApiClientReady(apiClient, subscriber); + return null; + } + }).when(apiClient).connect(); + + return apiClient; + } + }).when(baseObservable).createApiClient(Matchers.>any()); + + + } + + // Mock GoogleApiClient connection error behaviour + private void setupBaseObservableError(final BaseObservable baseObservable) { + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + final Subscriber subscriber = (Subscriber) invocation.getArguments()[0]; + + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + subscriber.onError(new GoogleAPIConnectionException("Error connecting to GoogleApiClient.", connectionResult)); + return null; + } + }).when(apiClient).connect(); + + return apiClient; + } + }).when(baseObservable).createApiClient(Matchers.>any()); + } + + @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 static void assertError(TestSubscriber sub, Class errorClass) { + sub.assertError(errorClass); + sub.assertNoValues(); + sub.assertUnsubscribed(); + } + + @SuppressWarnings("unchecked") + private static void assertSingleValue(TestSubscriber sub, Object value) { + sub.assertCompleted(); + sub.assertUnsubscribed(); + sub.assertValue(value); + } + + private static void assertNoValue(TestSubscriber sub) { + sub.assertCompleted(); + sub.assertUnsubscribed(); + sub.assertNoValues(); + } + + + ////////////////////// + // 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[] {})); + + setupBaseObservableError(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, GoogleAPIConnectionException.class); + } + + // CheckConnectionCompletable + + @Test + public void CheckConnectionCompletable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + CheckConnectionObservable observable = spy(new CheckConnectionObservable(rxWear)); + + setupBaseObservableSuccess(observable); + Completable.fromObservable(Observable.create(observable)).subscribe(sub); + + sub.assertCompleted(); + sub.assertNoValues(); + } + + @Test + public void CheckConnectionCompletable_Error() { + TestSubscriber sub = new TestSubscriber<>(); + CheckConnectionObservable observable = spy(new CheckConnectionObservable(rxWear)); + + setupBaseObservableError(observable); + Completable.fromObservable(Observable.create(observable)).subscribe(sub); + + sub.assertError(GoogleAPIConnectionException.class); + sub.assertNoValues(); + } + + /************** + * CAPABILITY * + **************/ + + // CapabilityAddListenerObservable + + @Test + public void CapabilityAddListenerObservable_String_Success() { + TestSubscriber sub = new TestSubscriber<>(); + String capability = "capability"; + CapabilityListenerObservable observable = spy(new CapabilityListenerObservable(rxWear, capability, null, null, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(capabilityApi.addCapabilityListener(Matchers.any(GoogleApiClient.class), Matchers.any(CapabilityApi.CapabilityListener.class), Matchers.anyString())).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + sub.assertNoTerminalEvent(); + sub.assertNoValues(); + } + + @Test + public void CapabilityAddListenerObservable_String_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + String capability = "capability"; + CapabilityListenerObservable observable = spy(new CapabilityListenerObservable(rxWear, capability, null, null, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(capabilityApi.addCapabilityListener(Matchers.any(GoogleApiClient.class), Matchers.any(CapabilityApi.CapabilityListener.class), Matchers.anyString())).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void CapabilityAddListenerObservable_Uri_Success() { + TestSubscriber sub = new TestSubscriber<>(); + int flag = 0; + CapabilityListenerObservable observable = spy(new CapabilityListenerObservable(rxWear, null, uri, flag, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(capabilityApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(CapabilityApi.CapabilityListener.class), Matchers.any(Uri.class), Matchers.anyInt())).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + sub.assertNoTerminalEvent(); + sub.assertNoValues(); + } + + @Test + public void CapabilityAddListenerObservable_Uri_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + int flag = 0; + CapabilityListenerObservable observable = spy(new CapabilityListenerObservable(rxWear, null, uri, flag, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(capabilityApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(CapabilityApi.CapabilityListener.class), Matchers.any(Uri.class), Matchers.anyInt())).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // CapabilityGetAllObservable + + @Test + public void CapabilityGetAllObservable_Success() { + TestSubscriber> sub = new TestSubscriber<>(); + Map resultMap = new HashMap<>(); + CapabilityApi.GetAllCapabilitiesResult result = Mockito.mock(CapabilityApi.GetAllCapabilitiesResult.class); + int nodeFilter = 0; + CapabilityGetAllObservable observable = spy(new CapabilityGetAllObservable(rxWear, nodeFilter, null, null)); + + when(result.getAllCapabilities()).thenReturn(resultMap); + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(capabilityApi.getAllCapabilities(apiClient, nodeFilter)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, resultMap); + } + + @Test + public void CapabilityGetAllObservable_StatusException() { + TestSubscriber> sub = new TestSubscriber<>(); + Map resultMap = new HashMap<>(); + CapabilityApi.GetAllCapabilitiesResult result = Mockito.mock(CapabilityApi.GetAllCapabilitiesResult.class); + int nodeFilter = 0; + CapabilityGetAllObservable observable = spy(new CapabilityGetAllObservable(rxWear, nodeFilter, null, null)); + + when(result.getAllCapabilities()).thenReturn(resultMap); + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(capabilityApi.getAllCapabilities(apiClient, nodeFilter)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // CapabilityGetObservable + + @Test + public void CapabilityGetObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + CapabilityApi.GetCapabilityResult result = Mockito.mock(CapabilityApi.GetCapabilityResult.class); + int nodeFilter = 0; + String capability = "capability"; + CapabilityGetObservable observable = spy(new CapabilityGetObservable(rxWear, capability, nodeFilter, null, null)); + + when(result.getCapability()).thenReturn(capabilityInfo); + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(capabilityApi.getCapability(apiClient, capability, nodeFilter)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, capabilityInfo); + } + + @Test + public void CapabilityGetObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + CapabilityApi.GetCapabilityResult result = Mockito.mock(CapabilityApi.GetCapabilityResult.class); + int nodeFilter = 0; + String capability = "capability"; + CapabilityGetObservable observable = spy(new CapabilityGetObservable(rxWear, capability, nodeFilter, null, null)); + + when(result.getCapability()).thenReturn(capabilityInfo); + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(capabilityApi.getCapability(apiClient, capability, nodeFilter)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // CapabilityAddLocalObservable + + @Test + public void CapabilityAddLocalObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + CapabilityApi.AddLocalCapabilityResult result = Mockito.mock(CapabilityApi.AddLocalCapabilityResult.class); + String capability = "capability"; + CapabilityAddLocalObservable observable = spy(new CapabilityAddLocalObservable(rxWear, capability, null, null)); + + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(capabilityApi.addLocalCapability(apiClient, capability)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void CapabilityAddLocalObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + CapabilityApi.AddLocalCapabilityResult result = Mockito.mock(CapabilityApi.AddLocalCapabilityResult.class); + + String capability = "capability"; + CapabilityAddLocalObservable observable = spy(new CapabilityAddLocalObservable(rxWear, capability, null, null)); + + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(capabilityApi.addLocalCapability(apiClient, capability)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // CapabilityAddLocalObservable + + @Test + public void CapabilityRemoveLocalObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + CapabilityApi.RemoveLocalCapabilityResult result = Mockito.mock(CapabilityApi.RemoveLocalCapabilityResult.class); + String capability = "capability"; + CapabilityRemoveLocalObservable observable = spy(new CapabilityRemoveLocalObservable(rxWear, capability, null, null)); + + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(capabilityApi.removeLocalCapability(apiClient, capability)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void CapabilityRemoveLocalObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + CapabilityApi.RemoveLocalCapabilityResult result = Mockito.mock(CapabilityApi.RemoveLocalCapabilityResult.class); + String capability = "capability"; + CapabilityRemoveLocalObservable observable = spy(new CapabilityRemoveLocalObservable(rxWear, capability, null, null)); + + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(capabilityApi.removeLocalCapability(apiClient, capability)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + /*********** + * CHANNEL * + ***********/ + + // ChannelListenerObservable + + @Test + public void ChannelListenerObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelListenerObservable observable = spy(new ChannelListenerObservable(rxWear, null, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(channelApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(ChannelApi.ChannelListener.class))).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + sub.assertNoTerminalEvent(); + sub.assertNoValues(); + } + + @Test + public void ChannelListenerObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelListenerObservable observable = spy(new ChannelListenerObservable(rxWear, null, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(channelApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(ChannelApi.ChannelListener.class))).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void ChannelListenerObservable_Channel_Success() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelListenerObservable observable = spy(new ChannelListenerObservable(rxWear, channel, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(channel.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(ChannelApi.ChannelListener.class))).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + sub.assertNoTerminalEvent(); + sub.assertNoValues(); + } + + @Test + public void ChannelListenerObservable_Channel_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelListenerObservable observable = spy(new ChannelListenerObservable(rxWear, channel, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(channel.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(ChannelApi.ChannelListener.class))).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // ChannelCloseObservable + + @Test + public void ChannelCloseObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelCloseObservable observable = spy(new ChannelCloseObservable(rxWear, channel, null, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(channel.close(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void ChannelCloseObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelCloseObservable observable = spy(new ChannelCloseObservable(rxWear, channel, null, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(channel.close(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void ChannelCloseObservable_ErrorCode_Success() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelCloseObservable observable = spy(new ChannelCloseObservable(rxWear, channel, 1, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(channel.close(apiClient, 1)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void ChannelCloseObservable_ErrorCode_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelCloseObservable observable = spy(new ChannelCloseObservable(rxWear, channel, 1, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(channel.close(apiClient, 1)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // ChannelSendFileObservable + + @Test + public void ChannelSendFileObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelSendFileObservable observable = spy(new ChannelSendFileObservable(rxWear, channel, uri, null, null, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(channel.sendFile(apiClient, uri)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void ChannelSendFileObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelSendFileObservable observable = spy(new ChannelSendFileObservable(rxWear, channel, uri, null, null, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(channel.sendFile(apiClient, uri)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void ChannelSendFileObservable_OffsetLength_Success() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelSendFileObservable observable = spy(new ChannelSendFileObservable(rxWear, channel, uri, 1l, 2l, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(channel.sendFile(apiClient, uri, 1l, 2l)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void ChannelSendFileObservable_OffsetLength_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelSendFileObservable observable = spy(new ChannelSendFileObservable(rxWear, channel, uri, 1l, 2l, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(channel.sendFile(apiClient, uri, 1l, 2l)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // ChannelOpenObservable + + @Test + public void ChannelOpenObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelApi.OpenChannelResult result = Mockito.mock(ChannelApi.OpenChannelResult.class); + String nodeId = "nodeId"; + String path ="path"; + ChannelOpenObservable observable = spy(new ChannelOpenObservable(rxWear, nodeId, path, null, null)); + + when(result.getStatus()).thenReturn(status); + when(result.getChannel()).thenReturn(channel); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(channelApi.openChannel(apiClient, nodeId, path)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, channel); + } + + @Test + public void ChannelOpenObservable_Success_NoChannel() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelApi.OpenChannelResult result = Mockito.mock(ChannelApi.OpenChannelResult.class); + String nodeId = "nodeId"; + String path ="path"; + ChannelOpenObservable observable = spy(new ChannelOpenObservable(rxWear, nodeId, path, null, null)); + + when(result.getStatus()).thenReturn(status); + when(result.getChannel()).thenReturn(null); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(channelApi.openChannel(apiClient, nodeId, path)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + sub.assertCompleted(); + sub.assertUnsubscribed(); + sub.assertNoValues(); + } + + @Test + public void ChannelOpenObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelApi.OpenChannelResult result = Mockito.mock(ChannelApi.OpenChannelResult.class); + String nodeId = "nodeId"; + String path ="path"; + ChannelOpenObservable observable = spy(new ChannelOpenObservable(rxWear, nodeId, path, null, null)); + + when(result.getStatus()).thenReturn(status); + when(result.getChannel()).thenReturn(channel); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(channelApi.openChannel(apiClient, nodeId, path)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // ChannelGetInputStreamObservable + + @Test + public void ChannelGetInputStreamObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + Channel.GetInputStreamResult result = Mockito.mock(Channel.GetInputStreamResult.class); + InputStream inputStream = Mockito.mock(InputStream.class); + ChannelGetInputStreamObservable observable = spy(new ChannelGetInputStreamObservable(rxWear, channel, null, null)); + + when(result.getInputStream()).thenReturn(inputStream); + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(channel.getInputStream(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, inputStream); + } + + @Test + public void ChannelGetInputStreamObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + Channel.GetInputStreamResult result = Mockito.mock(Channel.GetInputStreamResult.class); + InputStream inputStream = Mockito.mock(InputStream.class); + ChannelGetInputStreamObservable observable = spy(new ChannelGetInputStreamObservable(rxWear, channel, null, null)); + + when(result.getInputStream()).thenReturn(inputStream); + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(channel.getInputStream(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // ChannelGetOutputStreamObservable + + @Test + public void ChannelGetOutputStreamObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + Channel.GetOutputStreamResult result = Mockito.mock(Channel.GetOutputStreamResult.class); + OutputStream outputStream = Mockito.mock(OutputStream.class); + ChannelGetOutputStreamObservable observable = spy(new ChannelGetOutputStreamObservable(rxWear, channel, null, null)); + + when(result.getOutputStream()).thenReturn(outputStream); + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(channel.getOutputStream(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, outputStream); + } + + @Test + public void ChannelGetOutputStreamObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + Channel.GetOutputStreamResult result = Mockito.mock(Channel.GetOutputStreamResult.class); + OutputStream outputStream = Mockito.mock(OutputStream.class); + ChannelGetOutputStreamObservable observable = spy(new ChannelGetOutputStreamObservable(rxWear, channel, null, null)); + + when(result.getOutputStream()).thenReturn(outputStream); + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(channel.getOutputStream(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // ChannelReceiveFileObservable + + @Test + public void ChannelReceiveFileObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelReceiveFileObservable observable = spy(new ChannelReceiveFileObservable(rxWear, channel, uri, false, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(channel.receiveFile(apiClient, uri, false)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, status); + } + + @Test + public void ChannelReceiveFileObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + ChannelReceiveFileObservable observable = spy(new ChannelReceiveFileObservable(rxWear, channel, uri, false, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(channel.receiveFile(apiClient, uri, false)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + /******** + * DATA * + ********/ + + // DataListenerObservable + + @Test + public void DataListenerObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + DataListenerObservable observable = spy(new DataListenerObservable(rxWear, null, null, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(dataApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(DataApi.DataListener.class))).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + sub.assertNoTerminalEvent(); + sub.assertNoValues(); + } + + @Test + public void DataListenerObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + DataListenerObservable observable = spy(new DataListenerObservable(rxWear, null, null, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(dataApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(DataApi.DataListener.class))).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void DataListenerObservable_Uri_Success() { + TestSubscriber sub = new TestSubscriber<>(); + int filterType = 0; + DataListenerObservable observable = spy(new DataListenerObservable(rxWear, uri, filterType, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(dataApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(DataApi.DataListener.class), Matchers.any(Uri.class), Matchers.anyInt())).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + sub.assertNoTerminalEvent(); + sub.assertNoValues(); + } + + @Test + public void DataListenerObservable_Uri_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + int filterType = 0; + DataListenerObservable observable = spy(new DataListenerObservable(rxWear, uri, filterType, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(dataApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(DataApi.DataListener.class), Matchers.any(Uri.class), Matchers.anyInt())).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // DataDeleteItemsObservable + + @Test + public void DataDeleteItemsObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + DataApi.DeleteDataItemsResult result = Mockito.mock(DataApi.DeleteDataItemsResult.class); + DataDeleteItemsObservable observable = spy(new DataDeleteItemsObservable(rxWear, uri, null, null, null)); + + when(result.getStatus()).thenReturn(status); + when(result.getNumDeleted()).thenReturn(1); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(dataApi.deleteDataItems(apiClient, uri)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, 1); + } + + @Test + public void DataDeleteItemsObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + DataApi.DeleteDataItemsResult result = Mockito.mock(DataApi.DeleteDataItemsResult.class); + DataDeleteItemsObservable observable = spy(new DataDeleteItemsObservable(rxWear, uri, null, null, null)); + + when(result.getStatus()).thenReturn(status); + when(result.getNumDeleted()).thenReturn(1); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(dataApi.deleteDataItems(apiClient, uri)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void DataDeleteItemsObservable_FilterType_Success() { + TestSubscriber sub = new TestSubscriber<>(); + DataApi.DeleteDataItemsResult result = Mockito.mock(DataApi.DeleteDataItemsResult.class); + int filterType = 0; + DataDeleteItemsObservable observable = spy(new DataDeleteItemsObservable(rxWear, uri, filterType, null, null)); + + when(result.getStatus()).thenReturn(status); + when(result.getNumDeleted()).thenReturn(1); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(dataApi.deleteDataItems(apiClient, uri, filterType)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, 1); + } + + @Test + public void DataDeleteItemsObservable_FilterType_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + DataApi.DeleteDataItemsResult result = Mockito.mock(DataApi.DeleteDataItemsResult.class); + int filterType = 0; + DataDeleteItemsObservable observable = spy(new DataDeleteItemsObservable(rxWear, uri, filterType, null, null)); + + when(result.getStatus()).thenReturn(status); + when(result.getNumDeleted()).thenReturn(1); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(dataApi.deleteDataItems(apiClient, uri, filterType)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // DataPutItemObservable + + @Test + public void DataPutItemObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + PutDataRequest putDataRequest = Mockito.mock(PutDataRequest.class); + DataApi.DataItemResult result = Mockito.mock(DataApi.DataItemResult.class); + DataPutItemObservable observable = spy(new DataPutItemObservable(rxWear, putDataRequest, null, null)); + + when(result.getStatus()).thenReturn(status); + when(result.getDataItem()).thenReturn(dataItem); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(dataApi.putDataItem(apiClient, putDataRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, dataItem); + } + + @Test + public void DataPutItemObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + PutDataRequest putDataRequest = Mockito.mock(PutDataRequest.class); + DataApi.DataItemResult result = Mockito.mock(DataApi.DataItemResult.class); + DataPutItemObservable observable = spy(new DataPutItemObservable(rxWear, putDataRequest, null, null)); + + when(result.getStatus()).thenReturn(status); + when(result.getDataItem()).thenReturn(dataItem); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(dataApi.putDataItem(apiClient, putDataRequest)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // DataGetItemsObservable + + @Test + public void DataGetItemsObservable_Uri_FilterType_Success() { + TestSubscriber sub = new TestSubscriber<>(); + int filterType = 0; + DataGetItemsObservable observable = spy(new DataGetItemsObservable(rxWear, uri, filterType, null, null)); + + when(dataItemBuffer.getCount()).thenReturn(0); + when(dataItemBuffer.getStatus()).thenReturn(status); + setPendingResultValue(dataItemBuffer); + when(status.isSuccess()).thenReturn(true); + when(dataApi.getDataItems(apiClient, uri, filterType)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertNoValue(sub); + } + + @Test + public void DataGetItemsObservable_Uri_FilterType_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + int filterType = 0; + DataGetItemsObservable observable = spy(new DataGetItemsObservable(rxWear, uri, filterType, null, null)); + + when(dataItemBuffer.getCount()).thenReturn(0); + when(dataItemBuffer.getStatus()).thenReturn(status); + setPendingResultValue(dataItemBuffer); + when(status.isSuccess()).thenReturn(false); + when(dataApi.getDataItems(apiClient, uri, filterType)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void DataGetItemsObservable_Uri_Success() { + TestSubscriber sub = new TestSubscriber<>(); + DataGetItemsObservable observable = spy(new DataGetItemsObservable(rxWear, uri, null, null, null)); + + when(dataItemBuffer.getCount()).thenReturn(0); + when(dataItemBuffer.getStatus()).thenReturn(status); + setPendingResultValue(dataItemBuffer); + when(status.isSuccess()).thenReturn(true); + when(dataApi.getDataItems(apiClient, uri)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertNoValue(sub); + } + + @Test + public void DataGetItemsObservable_Uri_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + DataGetItemsObservable observable = spy(new DataGetItemsObservable(rxWear, uri, null, null, null)); + + when(dataItemBuffer.getCount()).thenReturn(0); + when(dataItemBuffer.getStatus()).thenReturn(status); + setPendingResultValue(dataItemBuffer); + when(status.isSuccess()).thenReturn(false); + when(dataApi.getDataItems(apiClient, uri)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void DataGetItemsObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + DataGetItemsObservable observable = spy(new DataGetItemsObservable(rxWear, uri, null, null, null)); + + when(dataItemBuffer.getCount()).thenReturn(0); + when(dataItemBuffer.getStatus()).thenReturn(status); + setPendingResultValue(dataItemBuffer); + when(status.isSuccess()).thenReturn(true); + when(dataApi.getDataItems(apiClient, uri)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertNoValue(sub); + } + + @Test + public void DataGetItemsObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + DataGetItemsObservable observable = spy(new DataGetItemsObservable(rxWear, null, null, null, null)); + + when(dataItemBuffer.getCount()).thenReturn(0); + when(dataItemBuffer.getStatus()).thenReturn(status); + setPendingResultValue(dataItemBuffer); + when(status.isSuccess()).thenReturn(false); + when(dataApi.getDataItems(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // DataGetFdForAssetObservable + + @Test + public void DataGetFdForAssetObservable_DataItemAsset_Success() { + TestSubscriber sub = new TestSubscriber<>(); + DataItemAsset dataItemAsset = Mockito.mock(DataItemAsset.class); + DataApi.GetFdForAssetResult result = Mockito.mock(DataApi.GetFdForAssetResult.class); + DataGetFdForAssetObservable observable = spy(new DataGetFdForAssetObservable(rxWear, dataItemAsset, null, null, null)); + + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(dataApi.getFdForAsset(apiClient, dataItemAsset)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, result); + } + + @Test + public void DataGetFdForAssetObservable_DataItemAsset_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + DataItemAsset dataItemAsset = Mockito.mock(DataItemAsset.class); + DataApi.GetFdForAssetResult result = Mockito.mock(DataApi.GetFdForAssetResult.class); + DataGetFdForAssetObservable observable = spy(new DataGetFdForAssetObservable(rxWear, dataItemAsset, null, null, null)); + + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(dataApi.getFdForAsset(apiClient, dataItemAsset)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void DataGetFdForAssetObservable_Asset_Success() { + TestSubscriber sub = new TestSubscriber<>(); + Asset asset = Mockito.mock(Asset.class); + DataApi.GetFdForAssetResult result = Mockito.mock(DataApi.GetFdForAssetResult.class); + DataGetFdForAssetObservable observable = spy(new DataGetFdForAssetObservable(rxWear, null, asset, null, null)); + + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(dataApi.getFdForAsset(apiClient, asset)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, result); + } + + @Test + public void DataGetFdForAssetObservable_Asset_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + Asset asset = Mockito.mock(Asset.class); + DataApi.GetFdForAssetResult result = Mockito.mock(DataApi.GetFdForAssetResult.class); + DataGetFdForAssetObservable observable = spy(new DataGetFdForAssetObservable(rxWear, null, asset, null, null)); + + when(result.getStatus()).thenReturn(status); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(dataApi.getFdForAsset(apiClient, asset)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + + /*********** + * MESSAGE * + ***********/ + + // MessageListenerObservable + + @Test + public void MessageListenerObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + MessageListenerObservable observable = spy(new MessageListenerObservable(rxWear, null, null, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(messageApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(MessageApi.MessageListener.class))).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + sub.assertNoTerminalEvent(); + sub.assertNoValues(); + } + + @Test + public void MessageListenerObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + MessageListenerObservable observable = spy(new MessageListenerObservable(rxWear, null, null, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(messageApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(MessageApi.MessageListener.class))).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + @Test + public void MessageListenerObservable_Uri_Success() { + TestSubscriber sub = new TestSubscriber<>(); + int filterType = 0; + MessageListenerObservable observable = spy(new MessageListenerObservable(rxWear, uri, filterType, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(messageApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(MessageApi.MessageListener.class), Matchers.any(Uri.class), Matchers.anyInt())).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + sub.assertNoTerminalEvent(); + sub.assertNoValues(); + } + + @Test + public void MessageListenerObservable_Uri_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + int filterType = 0; + MessageListenerObservable observable = spy(new MessageListenerObservable(rxWear, uri, filterType, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(messageApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(MessageApi.MessageListener.class), Matchers.any(Uri.class), Matchers.anyInt())).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // MessageSendObservable + + @Test + public void MessageSendObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + MessageApi.SendMessageResult result = Mockito.mock(MessageApi.SendMessageResult.class); + String nodeId = "nodeId"; + String path = "path"; + byte[] data = new byte[] {}; + MessageSendObservable observable = spy(new MessageSendObservable(rxWear, nodeId, path, data, null, null)); + + when(result.getStatus()).thenReturn(status); + when(result.getRequestId()).thenReturn(1); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(messageApi.sendMessage(apiClient, nodeId, path, data)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, 1); + } + + @Test + public void MessageSendObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + MessageApi.SendMessageResult result = Mockito.mock(MessageApi.SendMessageResult.class); + String nodeId = "nodeId"; + String path = "path"; + byte[] data = new byte[] {}; + MessageSendObservable observable = spy(new MessageSendObservable(rxWear, nodeId, path, data, null, null)); + + when(result.getStatus()).thenReturn(status); + when(result.getRequestId()).thenReturn(1); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(messageApi.sendMessage(apiClient, nodeId, path, data)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + /******** + * NODE * + ********/ + + // NodeListenerObservable + + @Test + public void NodeListenerObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + NodeListenerObservable observable = spy(new NodeListenerObservable(rxWear, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(true); + when(nodeApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(NodeApi.NodeListener.class))).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + sub.assertNoTerminalEvent(); + sub.assertNoValues(); + } + + @Test + public void NodeListenerObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + NodeListenerObservable observable = spy(new NodeListenerObservable(rxWear, null, null)); + + setPendingResultValue(status); + when(status.isSuccess()).thenReturn(false); + when(nodeApi.addListener(Matchers.any(GoogleApiClient.class), Matchers.any(NodeApi.NodeListener.class))).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // NodeGetConnectedObservable + + @Test + public void NodeGetConnectedObservable_Success() { + TestSubscriber> sub = new TestSubscriber<>(); + NodeApi.GetConnectedNodesResult result = Mockito.mock(NodeApi.GetConnectedNodesResult.class); + NodeGetConnectedObservable observable = spy(new NodeGetConnectedObservable(rxWear, null, null)); + + List nodeList = new ArrayList<>(); + + when(result.getStatus()).thenReturn(status); + when(result.getNodes()).thenReturn(nodeList); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(nodeApi.getConnectedNodes(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, nodeList); + } + + @Test + public void NodeGetConnectedObservable_StatusException() { + TestSubscriber> sub = new TestSubscriber<>(); + NodeApi.GetConnectedNodesResult result = Mockito.mock(NodeApi.GetConnectedNodesResult.class); + NodeGetConnectedObservable observable = spy(new NodeGetConnectedObservable(rxWear, null, null)); + + List nodeList = new ArrayList<>(); + + when(result.getStatus()).thenReturn(status); + when(result.getNodes()).thenReturn(nodeList); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(nodeApi.getConnectedNodes(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertError(sub, StatusException.class); + } + + // NodeGetLocalObservable + + @Test + public void NodeGetLocalObservable_Success() { + TestSubscriber sub = new TestSubscriber<>(); + NodeApi.GetLocalNodeResult result = Mockito.mock(NodeApi.GetLocalNodeResult.class); + Node node = Mockito.mock(Node.class); + NodeGetLocalObservable observable = spy(new NodeGetLocalObservable(rxWear, null, null)); + + when(result.getStatus()).thenReturn(status); + when(result.getNode()).thenReturn(node); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(true); + when(nodeApi.getLocalNode(apiClient)).thenReturn(pendingResult); + + setupBaseObservableSuccess(observable); + Observable.create(observable).subscribe(sub); + + assertSingleValue(sub, node); + } + + @Test + public void NodeGetLocalObservable_StatusException() { + TestSubscriber sub = new TestSubscriber<>(); + NodeApi.GetLocalNodeResult result = Mockito.mock(NodeApi.GetLocalNodeResult.class); + Node node = Mockito.mock(Node.class); + NodeGetLocalObservable observable = spy(new NodeGetLocalObservable(rxWear, null, null)); + + when(result.getStatus()).thenReturn(status); + when(result.getNode()).thenReturn(node); + setPendingResultValue(result); + when(status.isSuccess()).thenReturn(false); + when(nodeApi.getLocalNode(apiClient)).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..21f5f56 --- /dev/null +++ b/sample/build.gradle @@ -0,0 +1,48 @@ +apply plugin: 'com.android.application' +apply plugin: 'me.tatarka.retrolambda' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + applicationId "com.patloew.rxwearsample" + minSdkVersion 14 + 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 { + wearApp project(':wearsample') + + 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:gridlayout-v7:23.2.0" + + compile project(':library') + //compile 'com.patloew.rxwear:rxwear:1.0.0' + + compile 'io.reactivex:rxjava:1.1.1' + compile 'io.reactivex:rxandroid:1.1.0' + compile 'com.jakewharton.rxbinding:rxbinding:0.4.0' + + compile 'com.google.android.gms:play-services-wearable: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..598ac78 --- /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/rxwearsample/MainActivity.java b/sample/src/main/java/com/patloew/rxwearsample/MainActivity.java new file mode 100644 index 0000000..5be258d --- /dev/null +++ b/sample/src/main/java/com/patloew/rxwearsample/MainActivity.java @@ -0,0 +1,125 @@ +package com.patloew.rxwearsample; + +import android.content.Context; +import android.os.Bundle; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.Snackbar; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.util.Log; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; + +import com.jakewharton.rxbinding.view.RxView; +import com.patloew.rxwear.GoogleAPIConnectionException; +import com.patloew.rxwear.RxWear; +import com.patloew.rxwear.transformers.DataItemGetSerializable; + +import rx.Observable; +import rx.subscriptions.CompositeSubscription; + +public class MainActivity extends AppCompatActivity { + + private CoordinatorLayout coordinatorLayout; + private EditText titleEditText; + private EditText messageEditText; + private Button sendButton; + private EditText persistentEditText; + private Button setPersistentButton; + + private CompositeSubscription subscription = new CompositeSubscription(); + private Observable validator; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + RxWear.init(this); + + coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator_layout); + titleEditText = (EditText) findViewById(R.id.et_title); + messageEditText = (EditText) findViewById(R.id.et_message); + sendButton = (Button) findViewById(R.id.bt_send); + persistentEditText = (EditText) findViewById(R.id.et_persistenttext); + setPersistentButton = (Button) findViewById(R.id.bt_set_persistent); + + subscription.add(RxView.clicks(sendButton) + .doOnNext(click -> hideKeyboard()) + .flatMap(click2 -> validate()) + .filter(isValid -> isValid) + .flatMap(valid -> + RxWear.Message.SendDataMap.toAllRemoteNodes("/message") + .putString("title", titleEditText.getText().toString()) + .putString("message", messageEditText.getText().toString()) + .toObservable() + ).subscribe(requestId -> Snackbar.make(coordinatorLayout, "Sent message", Snackbar.LENGTH_LONG).show(), + throwable -> { + Log.e("MainActivity", "Error on sending message", throwable); + + if(throwable instanceof GoogleAPIConnectionException) { + Snackbar.make(coordinatorLayout, "Android Wear app is not installed", Snackbar.LENGTH_LONG).show(); + } else { + Snackbar.make(coordinatorLayout, "Could not send message", Snackbar.LENGTH_LONG).show(); + } + }) + ); + + subscription.add(RxView.clicks(setPersistentButton) + .doOnNext(click -> hideKeyboard()) + .flatMap(click2 -> RxWear.Data.PutSerializable.urgentTo("/persistentText", persistentEditText.getText().toString())) + .subscribe(dataItem1 -> Snackbar.make(coordinatorLayout, "Set persistent text", Snackbar.LENGTH_LONG).show(), + throwable -> { + Log.e("MainActivity", "Error on setting persistent text", throwable); + + if(throwable instanceof GoogleAPIConnectionException) { + Snackbar.make(coordinatorLayout, "Android Wear app is not installed", Snackbar.LENGTH_LONG).show(); + } else { + Snackbar.make(coordinatorLayout, "Could not set persistent text", Snackbar.LENGTH_LONG).show(); + } + })); + + subscription.add(RxWear.Data.get() + .compose(DataItemGetSerializable.filterByPath("/persistentText")) + .subscribe(text -> persistentEditText.setText(text))); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + + if(subscription != null && !subscription.isUnsubscribed()) { + subscription.unsubscribe(); + } + } + + private void hideKeyboard() { + InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(sendButton.getWindowToken(), 0); + } + + private Observable validate() { + if(validator == null) { + validator = Observable.create((Observable.OnSubscribe) subscriber -> { + boolean valid = true; + + if(TextUtils.isEmpty(titleEditText.getText())) { + titleEditText.setError("Please enter title"); + valid = false; + } + + if(TextUtils.isEmpty(messageEditText.getText())) { + messageEditText.setError("Please enter message"); + valid = false; + } + + subscriber.onNext(valid); + subscriber.onCompleted(); + }); + } + + return validator; + } +} 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..bb6f9db --- /dev/null +++ b/sample/src/main/res/layout/activity_main.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + +