├── .gitignore
├── CHANGES.md
├── LICENSE
├── README.md
├── WebTestBot
└── index.html
├── build.gradle
├── ci-test.sh
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── respoke-sdk-android.iml
├── respokeSDK
├── build.gradle
├── gradle.properties
├── proguard-rules.pro
├── respoke-sdk.iml
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── digium
│ │ └── respokesdk
│ │ ├── Respoke.java
│ │ ├── RespokeCall.java
│ │ ├── RespokeClient.java
│ │ ├── RespokeConnection.java
│ │ ├── RespokeConversationReadStatus.java
│ │ ├── RespokeDirectConnection.java
│ │ ├── RespokeEndpoint.java
│ │ ├── RespokeGroup.java
│ │ ├── RespokeGroupMessage.java
│ │ ├── RespokeSignalingChannel.java
│ │ ├── RespokeWorkerThread.java
│ │ └── RestAPI
│ │ ├── APIDoOpen.java
│ │ ├── APIGetToken.java
│ │ └── APITransaction.java
│ └── res
│ ├── drawable-hdpi
│ └── ic_launcher.png
│ ├── drawable-mdpi
│ └── ic_launcher.png
│ ├── drawable-xhdpi
│ └── ic_launcher.png
│ ├── drawable-xxhdpi
│ └── ic_launcher.png
│ └── values
│ └── strings.xml
├── respokeSDKTest
├── android-wait-for-emulator
├── build.gradle
├── proguard-rules.pro
├── respokeSDKTest.iml
├── runtests.sh
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── digium
│ │ └── respokesdktest
│ │ ├── RespokeActivityTestCase.java
│ │ ├── RespokeTestCase.java
│ │ ├── functional
│ │ ├── CallingTests.java
│ │ ├── ConnectionTests.java
│ │ ├── DirectConnectionTests.java
│ │ ├── GroupTests.java
│ │ ├── HangupTests.java
│ │ ├── MessagingTests.java
│ │ └── PresenceTests.java
│ │ └── unit
│ │ ├── RespokeCallTests.java
│ │ ├── RespokeClientTests.java
│ │ ├── RespokeEndpointTests.java
│ │ ├── RespokeGroupTests.java
│ │ └── RespokeTests.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── digium
│ │ └── respokesdktest
│ │ └── MainActivity.java
│ └── res
│ ├── layout
│ └── activity_main.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | respokeSDK/build
2 | respokeSDK/respokeSDK.iml
3 | .DS_Store
4 | .gradle/
5 | respokeSDKTest/build
6 | respokeSDK/gradle.properties
7 | build
8 | .idea
9 | local.properties
10 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | Respoke Android SDK Change Log
2 | ==============================
3 |
4 | v1.5.0
5 | ------
6 | * Moved RespokeClient.getGroupHistories() to a new route from a deprecated one.
7 | * Adds RespokeClient.getConversations() and .setConversationsRead() for server-side message history support.
8 |
9 | v1.4.1
10 | ------
11 | * Updated AndroidAsync dependency to 2.1.7
12 |
13 | v1.4.0
14 | ------
15 |
16 | * Added support for retrieving group message history, and marking a message to be persisted
17 | to the history. See commit 9a3f4e6 for details.
18 |
19 | v1.3.3
20 | ------
21 |
22 | * Removed unnecessary inclusion of Google Play Services. The SDK doesn't actually use it. Client
23 | apps should refer to the demo app for examples of how to use GCM (which relies on Google Play
24 | Services).
25 |
26 | v1.3.2
27 | ------
28 |
29 | * Fixed a crash that can occur intermittently during call setup
30 |
31 | v1.3.1
32 | ------
33 |
34 | * Fixed a crash that can occur if a hangup signal is received during key moments of call set up
35 |
36 | v1.3.0
37 | ------
38 |
39 | * Improved ice candidates signaling, resulting in faster call setup and inter-op support for
40 | clients that do not support trickle ice, such as chan_respoke.
41 |
42 | v1.2.0
43 | ------
44 |
45 | * Added Respoke-SDK header to websocket & http[s] requests, which includes the sdk version
46 | and the Android OS version making the request.
47 |
48 | v1.1.2
49 | ------
50 |
51 | * Fixed a packaging problem that was preventing the SDK from being installed from Maven
52 |
53 | v1.1.1
54 | ------
55 |
56 | * Added missing in-line documentation to the public interfaces used by client applications.
57 |
58 | * Rearranged internally used methods for clarity
59 |
60 | v1.1.0
61 | ------
62 |
63 | * This version of the Android SDK now uses a customized version of the AndroidAsync library
64 | dependency as a submodule rather than has a Gradle dependency. If you are working with the SDK
65 | code directly, note that you now need to use the `--recursive` flag when cloning the repository as
66 | described in the README.
67 |
68 | * ***breaking*** Changed the `RespokeEndpoint.sendMessage()` method to now have 4 params,
69 | where the 3rd param is now `Boolean ccSelf`. This param indicates to the Respoke service whether the
70 | message should be sent to other connections of the sender's endpointId or not.
71 |
72 | * ***breaking*** Changed the `onMessage()` method of the `RespokeClient.Listener` interface to now require
73 | a 5th param, `Boolean didSend`. This param indicates to the listener whether the message received
74 | was *from* the localEndpoint *sent to* the specified endpoint (didSend=false), or was *from* the
75 | specified endpoint *sent to* the localEndpoint (didSend=true). This change was required to support
76 | messages that could be coming from another connection of the same endpointID that the current client
77 | is connected as (ccSelf).
78 |
79 | * ***breaking*** Changed the `onMessage()` method of the `RespokeEndpoint.Listener` interface to now
80 | require a 4th param, `Boolean didSend`. This param has the same meaning as the one added to
81 | `RespokeClient.Listener#onMessage`.
82 |
83 | * Added conference call support. Call the new `RespokeClient.joinConference()` method to create a
84 | RespokeCall instance that will join the multiparty audio conference specified by the conferenceID
85 | parameter.
86 |
87 | * Declining an incoming call by calling the `RespokeCall.hangup()` method in the Android SDK will now also decline that call on any other clients that have logged in to Respoke using the same endpoint ID (Android or otherwise)
88 |
89 | * Fixed a bug that could prevent the SDK from registering for push notifications.
90 |
91 | * Fixed several intermittent crashes related to hanging up a call.
92 |
93 | * Fixed a crash that can occur when an application using the Respoke SDK is pushed into or restored
94 | from the background.
95 |
96 | v1.0.4
97 | ------
98 |
99 | * Added a new "push" parameter to `RespokeGroup.sendMessage()` and `RespokeEndpoint.sendMessage()`
100 | that allows you to indicate if a specific message should be considered (true) or ignored (false) by
101 | the push notification service. Use this to explicitly ignore certain messages (like meta data) that
102 | should never be sent to mobile devices as a push notification.
103 |
104 | * Added a new `Respoke.unregisterPushServices()` method that allows you to explicitly unregister a
105 | device from receiving push notifications in the future.
106 |
107 | * Updated the AndroidAsync, Google Play Services, and App Support dependency libraries to the latest
108 | available versions
109 |
110 | * Improved the instructions in the README on how to run the SDK test cases
111 |
112 | * Miscellaneous test case fixes
113 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015, Digium, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Respoke SDK for Android
2 | =======================
3 |
4 | The Respoke SDK for Android makes it easy to add live voice, video, text, and data features to your mobile app. For information on how to use the SDK, take a look at our developer documentation and sample apps here:
5 |
6 | [https://docs.respoke.io/](https://docs.respoke.io/)
7 |
8 | Installing the SDK
9 | =============
10 |
11 | The Respoke Android SDK is available to install via the Maven Central repository.
12 | You can [download the latest JAR directly](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.digium.respoke%22%20AND%20a%3A%22respoke-sdk%22), or install via Maven:
13 |
14 | ```
15 |
16 | com.digium.respoke
17 | respoke-sdk
18 | (insert latest version)
19 |
20 | ```
21 |
22 | Gradle:
23 |
24 | ```
25 | dependencies {
26 | compile 'com.digium.respoke:respoke-sdk:1.+'
27 | }
28 | ```
29 | Building the SDK from source
30 | ============================
31 |
32 | You must use JDK 1.8.
33 |
34 | From the root directory:
35 | ```
36 | ./gradlew build
37 | ```
38 |
39 | NOTE: The javadoc generation task will complain with errors, but it will still generate the javadoc jar file.
40 |
41 | Contributing
42 | ============
43 |
44 | We welcome pull requests to improve the SDK for everyone. When submitting changes, please make sure you have run the SDK test cases before submitting and added/modified any tests that are affected by your improvements.
45 |
46 | Running the SDK test cases
47 | ==========================
48 |
49 | The SDK test cases require UI elements and so are contained in a test application named "RespokeSDKTest" in this repo. To run the test cases, do the following:
50 |
51 |
52 | 1) Create a Respoke developer account and define a Respoke application in the [developer console](https://portal.respoke.io/#/signup). Make a note of the **application ID** for the Respoke Application you created.
53 |
54 | 2) Clone this repo onto your development machine.
55 |
56 | 3) Open Android Studio, choose "Import Project" and select the root directory of the repo.
57 |
58 | 4) Open RespokeTestCase.java and change the value of the static string `TEST_APP_ID` with the Respoke application ID you received in step 1.
59 |
60 | 5) Start the web TestBot in either Chrome or Firefox as described in the section "Starting the Web TestBot" below, passing your Respoke application ID as a parameter on the URL.
61 |
62 | 6) In Android Studio, open the 'Run' menu and select "Edit Configurations". In the upper left corner of the dialog that appears, press the '+' button to create a new run configuration and select 'Android Tests'.
63 |
64 | 7) Name the new test configuration "All Tests", select the "respokeSDKTest" module from the drop-down box, and choose the settings in the "Target Device" box that make the most sense for your test set up. save the new configuration. You only have to do this once, in the future you can just select the existing configuration to run the tests.
65 |
66 | 8) Open the "Run" menu again, and choose "debug…". Select the "All Tests" configuration to run the test cases, displaying the results inside of Android Studio. You will also see debug messages and video displayed in the web browser running the TestBot.
67 |
68 | Starting the Web TestBot
69 | ========================
70 |
71 | The functional test cases that use RespokeCall require a specific Web application based on Respoke.js that is set up to automatically respond to certain actions that the SDK test cases perform. Because the web application will use audio and video, it requires special user permissions from browsers that support WebRTC and typically requires user interaction. Therefore it must run from either the context of a web server, or by loading the html file from the file system with specific command line parameters for Chrome.
72 |
73 | Additionally, the Android Studio test project has been set up to expect that the web application will connect to Respoke with a specific endpoint ID in the following format:
74 |
75 | testbot-username
76 |
77 | This username is the user that you are logged into your development computer with when you run the tests. This is done to avoid conflicts that can occur when multiple developers are running multiple instances of the test web application simultaneously.
78 |
79 | To set up your system to perform these tests, do one of the following:
80 |
81 | #### A) Load the html from a file with Chrome.
82 |
83 |
84 | 1) You can use command line parameters to load the test bot with Chrome tell it to use a fake audio and video source during testing. On Mac OS, the command would look like this:
85 |
86 | $ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
87 | --use-fake-ui-for-media-stream \
88 | --use-fake-device-for-media-stream \
89 | --allow-file-access-from-files \
90 | ./WebTestBot/index.html &
91 |
92 | 2) Once the file has loaded, append your local username and Respoke application ID to the URL to match what Android Studio will search for as the tests run:
93 |
94 | file:///respoke-android-sdk/WebTestBot/index.html#?un=mymacusername&app_id=my-respoke-app-id
95 |
96 | 3) Run the SDK test cases
97 |
98 |
99 |
100 | #### B) Run with a local web server.
101 |
102 |
103 | 1) Install http-server
104 |
105 | $ sudo npm i -g http-server
106 |
107 | 2) Start http-server from the testbot directory:
108 |
109 | $ cd WebTestBot/
110 | $ http-server
111 |
112 | 3) Start Chrome using command line parameters to use fake audio/video and auto accept media permissions so that no human interaction is required:
113 |
114 | $ /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --use-fake-ui-for-media-stream --use-fake-device-for-media-stream
115 |
116 | This can alternately be done with Firefox by navigating to "about:config" and then setting the "media.navigator.permission.disabled" option to TRUE
117 |
118 | 4) Open the TestBot in a Chrome tab by loading http://localhost:8080/#?un=mymacusername&app_id=my-respoke-app-id
119 |
120 | 5) Run the SDK test cases
121 |
122 |
123 |
124 | License
125 | =======
126 |
127 | The Respoke SDK and demo applications are licensed under the MIT license. Please see the [LICENSE](LICENSE) file for details.
128 |
--------------------------------------------------------------------------------
/WebTestBot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
33 |
34 |
177 |
178 |
179 |
180 |
181 |
182 |
183 | Connected as {{username}}. Waiting for test users.
184 |
185 |
186 |
187 | Connecting the SDK autotest bot, please wait...
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 | {{message.endpointID}} - {{message.content}}
196 |
197 |
198 |
199 |
200 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.2'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/ci-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Test runner for the CI server
5 | #
6 |
7 | trap "on_exit" EXIT
8 |
9 | # Ensure cleanup runs on exit
10 | function on_exit()
11 | {
12 | if test -f chrome.pid; then
13 | kill $(cat chrome.pid)
14 | rm -f chrome.pid
15 | fi
16 | }
17 |
18 | set -ex
19 |
20 | # Kill lingering instances of Google Chrome
21 | killall -9 "Google Chrome" || true # ignore errors
22 | while test $(ps aux | grep ^bamboo | grep -i [c]hrome | wc -l) -ne 0; do
23 | sleep 1
24 | done
25 |
26 | # List the connected Android devices for debugging purposes
27 | adb devices
28 |
29 | # Launch Google Chrome
30 | "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
31 | --use-fake-ui-for-media-stream \
32 | --use-fake-device-for-media-stream \
33 | --allow-file-access-from-files \
34 | ./WebTestBot/index.html &
35 | echo $! > chrome.pid
36 |
37 | # Run the tests
38 | ./gradlew -p respokeSDKTest --stacktrace --info clean connectedAndroidTest
39 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Settings specified in this file will override any Gradle settings
5 | # configured through the IDE.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/respoke/respoke-sdk-android/34a15f0558d29b1f1bc8481bbc5c505e855e05ef/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Dec 01 14:46:15 CST 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/respoke-sdk-android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/respokeSDK/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.2'
9 | }
10 | }
11 |
12 | apply plugin: 'com.android.library'
13 |
14 | repositories {
15 | mavenCentral()
16 | jcenter()
17 | }
18 |
19 | android {
20 | compileSdkVersion 23
21 | buildToolsVersion '23.0.3'
22 |
23 | configurations {
24 | javadocDeps
25 | }
26 |
27 | defaultConfig {
28 | minSdkVersion 15
29 | targetSdkVersion 23
30 | }
31 |
32 | buildTypes {
33 | release {
34 | minifyEnabled false
35 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
36 | }
37 | }
38 | }
39 |
40 | dependencies {
41 | compile fileTree(dir: 'libs', include: ['*.jar'])
42 | compile 'com.android.support:appcompat-v7:23.2.0'
43 | compile 'io.pristine:libjingle:9340@aar'
44 | compile 'com.digium.respoke:AndroidAsync:2.1.7'
45 | }
46 |
47 | apply plugin: 'maven'
48 | apply plugin: 'signing'
49 |
50 | task androidJavadocs(type: Javadoc) {
51 | options.addBooleanOption('Xdoclint:none', true)
52 | source = android.sourceSets.main.java.source
53 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
54 | options.linkSource true
55 | destinationDir = file("../javadoc/")
56 | failOnError false
57 | }
58 |
59 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
60 | classifier = 'javadoc'
61 | from androidJavadocs.destinationDir
62 | }
63 |
64 | task androidSourcesJar(type: Jar) {
65 | classifier = 'sources'
66 | from android.sourceSets.main.java.source
67 | }
68 |
69 | artifacts {
70 | archives androidSourcesJar
71 | archives androidJavadocsJar
72 | }
73 |
74 | signing {
75 | required { gradle.taskGraph.hasTask("uploadArchives") }
76 | sign configurations.archives
77 | }
78 |
79 |
80 | group = "com.digium.respoke"
81 | version = "1.5.0"
82 |
83 | uploadArchives {
84 | repositories {
85 | mavenDeployer {
86 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
87 |
88 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
89 | authentication(userName: ossrhUsername, password: ossrhPassword)
90 | }
91 |
92 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
93 | authentication(userName: ossrhUsername, password: ossrhPassword)
94 | }
95 |
96 | pom.project {
97 | name 'Respoke Android SDK'
98 | artifactId 'respoke-sdk'
99 | packaging 'jar'
100 | description 'Add live voice, video, text and data features to your mobile app'
101 | url 'https://www.respoke.io/'
102 |
103 | scm {
104 | connection 'scm:git@github.com:respoke/respoke-sdk-android.git'
105 | developerConnection 'scm:git@github.com:respoke/respoke-sdk-android.git'
106 | url 'https://github.com/respoke/respoke-sdk-android'
107 | }
108 |
109 | licenses {
110 | license {
111 | name 'MIT License'
112 | url 'https://github.com/respoke/respoke-sdk-android/blob/master/LICENSE'
113 | }
114 | }
115 |
116 | developers {
117 | developer {
118 | id 'jpadams'
119 | name 'Respoke.io'
120 | email 'info@respoke.io'
121 | }
122 | }
123 | }
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/respokeSDK/gradle.properties:
--------------------------------------------------------------------------------
1 | signing.keyId=keyID
2 | signing.password=keyPassword
3 | signing.secretKeyRingFile=~/.gnupg/secring.gpg
4 |
5 | ossrhUsername=username
6 | ossrhPassword=password
--------------------------------------------------------------------------------
/respokeSDK/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | -keep class * extends java.util.ListResourceBundle {
20 | protected Object[][] getContents();
21 | }
22 |
23 | -keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable {
24 | public static final *** NULL;
25 | }
26 |
27 | -keepnames @com.google.android.gms.common.annotation.KeepName class *
28 | -keepclassmembernames class * {
29 | @com.google.android.gms.common.annotation.KeepName *;
30 | }
31 |
32 | -keepnames class * implements android.os.Parcelable {
33 | public static final ** CREATOR;
34 | }
--------------------------------------------------------------------------------
/respokeSDK/respoke-sdk.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/respokeSDK/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/respokeSDK/src/main/java/com/digium/respokesdk/Respoke.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdk;
12 |
13 | import android.content.Context;
14 | import android.os.Handler;
15 | import android.os.Looper;
16 |
17 | import org.webrtc.PeerConnectionFactory;
18 | import org.webrtc.VideoRendererGui;
19 |
20 | import java.util.ArrayList;
21 |
22 | /**
23 | * A global static class which provides access to the Respoke functionality.
24 | */
25 | public class Respoke {
26 |
27 | public final static int GUID_STRING_LENGTH = 36; // The length of GUID strings
28 |
29 | private static Respoke _instance;
30 | private static boolean factoryStaticInitialized;
31 | private String pushToken;
32 | private ArrayList instances;
33 | private Context context;
34 |
35 |
36 | /**
37 | * A listener interface to allow the receiver to be notified of the success or failure of an asynchronous operation
38 | */
39 | public interface TaskCompletionListener {
40 |
41 |
42 | /**
43 | * Receive a notification that the asynchronous operation completed successfully
44 | */
45 | void onSuccess();
46 |
47 |
48 | /**
49 | * Receive a notification that the asynchronous operation failed
50 | *
51 | * @param errorMessage A human-readable description of the error that was encountered
52 | */
53 | void onError(String errorMessage);
54 |
55 | }
56 |
57 |
58 | /**
59 | * A helper function to post success to a TaskCompletionListener on the UI thread
60 | *
61 | * @param completionListener The TaskCompletionListener to notify
62 | */
63 | public static void postTaskSuccess(final TaskCompletionListener completionListener) {
64 | new Handler(Looper.getMainLooper()).post(new Runnable() {
65 | @Override
66 | public void run() {
67 | if (null != completionListener) {
68 | completionListener.onSuccess();
69 | }
70 | }
71 | });
72 | }
73 |
74 |
75 | /**
76 | * A helper function to post an error message to a TaskCompletionListener on the UI thread
77 | *
78 | * @param completionListener The TaskCompletionListener to notify
79 | * @param errorMessage The error message to post
80 | */
81 | public static void postTaskError(final TaskCompletionListener completionListener, final String errorMessage) {
82 | new Handler(Looper.getMainLooper()).post(new Runnable() {
83 | @Override
84 | public void run() {
85 | if (null != completionListener) {
86 | completionListener.onError(errorMessage);
87 | }
88 | }
89 | });
90 | }
91 |
92 |
93 | /**
94 | * The private constructor for the Respoke singleton
95 | */
96 | private Respoke()
97 | {
98 | instances = new ArrayList();
99 | }
100 |
101 |
102 | /**
103 | * Retrieve the globally shared instance of the Respoke SDK
104 | *
105 | * @return Respoke SDK instance
106 | */
107 | public static Respoke sharedInstance()
108 | {
109 | if (_instance == null)
110 | {
111 | _instance = new Respoke();
112 | }
113 |
114 | return _instance;
115 | }
116 |
117 |
118 | /**
119 | * This is one of two possible entry points for interacting with the library. This method creates a new Client object
120 | * which represents your app's connection to the cloud infrastructure. This method does NOT automatically call the
121 | * client.connect() method after the client is created, so your app will need to call it when it is ready to
122 | * connect.
123 | * @param appContext The Android application Context
124 | * @return A Respoke Client instance
125 | */
126 | public RespokeClient createClient(Context appContext)
127 | {
128 | context = appContext;
129 |
130 | RespokeClient newClient = new RespokeClient();
131 | instances.add(newClient);
132 |
133 | return newClient;
134 | }
135 |
136 |
137 | /**
138 | * Unregister a client that is no longer active so that it's resources can be
139 | * deallocated
140 | *
141 | * @param client The client to unregister
142 | */
143 | public void unregisterClient(RespokeClient client) {
144 | instances.remove(client);
145 | }
146 |
147 |
148 | /**
149 | * Create a globally unique identifier for naming instances
150 | *
151 | * @return New globally unique identifier
152 | */
153 | public static String makeGUID() {
154 | String uuid = "";
155 | String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
156 | int rnd = 0;
157 | int r;
158 |
159 | for (int i = 0; i < GUID_STRING_LENGTH; i += 1) {
160 | if (i == 8 || i == 13 || i == 18 || i == 23) {
161 | uuid = uuid + "-";
162 | } else if (i == 14) {
163 | uuid = uuid + "4";
164 | } else {
165 | if (rnd <= 0x02) {
166 | rnd = (int) (0x2000000 + Math.round(java.lang.Math.random() * 0x1000000));
167 | }
168 | r = rnd & 0xf;
169 | rnd = rnd >> 4;
170 |
171 | if (i == 19) {
172 | uuid = uuid + chars.charAt((r & 0x3) | 0x8);
173 | } else {
174 | uuid = uuid + chars.charAt(r);
175 | }
176 | }
177 | }
178 | return uuid;
179 | }
180 |
181 |
182 | /**
183 | * Notify the Respoke SDK that this device should register itself for push notifications
184 | *
185 | * @param token The token that identifies the device to GCMS.
186 | */
187 | public void registerPushToken(String token) {
188 | pushToken = token;
189 |
190 | if (instances.size() > 0) {
191 | registerPushServices();
192 | }
193 | }
194 |
195 |
196 | /**
197 | * Unregister this device from the Respoke push notification service and stop any future notifications until it is re-registered
198 | *
199 | * @param completionListener A listener to be notified of the success of the asynchronous unregistration operation
200 | */
201 | public void unregisterPushServices(TaskCompletionListener completionListener) {
202 | RespokeClient activeInstance = null;
203 |
204 | // If there are already client instances running, check if any of them have already connected
205 | for (RespokeClient eachInstance : instances) {
206 | if (eachInstance.isConnected()) {
207 | // The push service only supports one endpoint per device, so the token only needs to be registered for the first active client (if there is more than one)
208 | activeInstance = eachInstance;
209 | break;
210 | }
211 | }
212 |
213 | if (null != activeInstance) {
214 | activeInstance.unregisterFromPushServices(completionListener);
215 | } else {
216 | postTaskError(completionListener, "There is no active client to unregister");
217 | }
218 | }
219 |
220 |
221 | /**
222 | * Notify the shared SDK instance that the specified client has connected. This is for internal use only, and should never be called by your client application.
223 | *
224 | * @param client The client that just connected
225 | */
226 | public void clientConnected(RespokeClient client) {
227 | if (null != pushToken) {
228 | registerPushServices();
229 | }
230 |
231 | if (!factoryStaticInitialized) {
232 | // Perform a one-time WebRTC global initialization
233 | PeerConnectionFactory.initializeAndroidGlobals(context, true, true, true, VideoRendererGui.getEGLContext());
234 | factoryStaticInitialized = true;
235 | }
236 | }
237 |
238 |
239 | //** Private methods
240 |
241 |
242 | /**
243 | * Attempt to register push services for this device
244 | */
245 | private void registerPushServices() {
246 | RespokeClient activeInstance = null;
247 |
248 | // If there are already client instances running, check if any of them have already connected
249 | for (RespokeClient eachInstance : instances) {
250 | if (eachInstance.isConnected()) {
251 | // The push service only supports one endpoint per device, so the token only needs to be registered for the first active client (if there is more than one)
252 | activeInstance = eachInstance;
253 | }
254 | }
255 |
256 | if (null != activeInstance) {
257 | // Notify the Respoke servers that this device is eligible to receive notifications directed at this endpointID
258 | activeInstance.registerPushServicesWithToken(pushToken);
259 | }
260 | }
261 |
262 | }
263 |
--------------------------------------------------------------------------------
/respokeSDK/src/main/java/com/digium/respokesdk/RespokeConnection.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdk;
12 |
13 | import java.lang.ref.WeakReference;
14 |
15 | /**
16 | * Represents remote Connections which belong to an Endpoint. An Endpoint can be authenticated from multiple devices,
17 | * browsers, or tabs. Each of these separate authentications is a Connection. The client can interact
18 | * with connections by calling them or sending them messages.
19 | */
20 | public class RespokeConnection {
21 |
22 | public String connectionID;
23 | private WeakReference endpointReference;
24 | public Object presence;
25 |
26 |
27 | /**
28 | * The constructor for this class
29 | *
30 | * @param newConnectionID The ID for this connection
31 | * @param newEndpoint The endpoint to which this connection belongs
32 | */
33 | public RespokeConnection(String newConnectionID, RespokeEndpoint newEndpoint) {
34 | connectionID = newConnectionID;
35 | endpointReference = new WeakReference(newEndpoint);
36 | }
37 |
38 |
39 | /**
40 | * Get the endpoint to which this connection belongs
41 | *
42 | * @return The endpoint instance
43 | */
44 | public RespokeEndpoint getEndpoint() {
45 | return endpointReference.get();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/respokeSDK/src/main/java/com/digium/respokesdk/RespokeConversationReadStatus.java:
--------------------------------------------------------------------------------
1 | package com.digium.respokesdk;
2 |
3 | import java.util.Date;
4 |
5 | import static android.R.id.message;
6 |
7 | /**
8 | *
9 | */
10 |
11 | public class RespokeConversationReadStatus {
12 | public String groupId;
13 | public Date timestamp;
14 |
15 | public RespokeConversationReadStatus(String groupId, Date timestamp) {
16 | this.groupId = groupId;
17 | this.timestamp = timestamp;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/respokeSDK/src/main/java/com/digium/respokesdk/RespokeDirectConnection.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdk;
12 |
13 | import android.content.Context;
14 | import android.os.Handler;
15 | import android.os.Looper;
16 |
17 | import org.json.JSONException;
18 | import org.json.JSONObject;
19 | import org.webrtc.DataChannel;
20 | import org.webrtc.PeerConnection;
21 |
22 | import java.io.UnsupportedEncodingException;
23 | import java.lang.ref.WeakReference;
24 | import java.nio.ByteBuffer;
25 | import java.nio.CharBuffer;
26 | import java.nio.charset.CharacterCodingException;
27 | import java.nio.charset.Charset;
28 | import java.nio.charset.CharsetDecoder;
29 |
30 | /**
31 | * A direct connection via RTCDataChannel, including state and path negotation.
32 | */
33 | public class RespokeDirectConnection implements org.webrtc.DataChannel.Observer {
34 |
35 | private WeakReference listenerReference;
36 | private WeakReference callReference;
37 | private DataChannel dataChannel;
38 |
39 |
40 | /**
41 | * A listener interface to notify the receiver of events occurring with the direct connection
42 | */
43 | public interface Listener {
44 |
45 | /**
46 | * The direct connection setup has begun. This does NOT mean it's ready to send messages yet. Listen to
47 | * onOpen for that notification.
48 | *
49 | * @param sender The direct connection for which the event occurred
50 | */
51 | public void onStart(RespokeDirectConnection sender);
52 |
53 | /**
54 | * Called when the direct connection is opened.
55 | *
56 | * @param sender The direct connection for which the event occurred
57 | */
58 | public void onOpen(RespokeDirectConnection sender);
59 |
60 | /**
61 | * Called when the direct connection is closed.
62 | *
63 | * @param sender The direct connection for which the event occurred
64 | */
65 | public void onClose(RespokeDirectConnection sender);
66 |
67 | /**
68 | * Called when a message is received over the direct connection.
69 | * @param message The message received.
70 | * @param sender The direct connection for which the event occurred
71 | */
72 | public void onMessage(String message, RespokeDirectConnection sender);
73 |
74 | }
75 |
76 |
77 | /**
78 | * The constructor for this class
79 | *
80 | * @param call The call instance with which this direct connection is associated
81 | */
82 | public RespokeDirectConnection(RespokeCall call) {
83 | callReference = new WeakReference(call);
84 | }
85 |
86 |
87 | /**
88 | * Set a receiver for the Listener interface
89 | *
90 | * @param listener The new receiver for events from the Listener interface for this instance
91 | */
92 | public void setListener(Listener listener) {
93 | if (null != listener) {
94 | listenerReference = new WeakReference(listener);
95 | } else {
96 | listenerReference = null;
97 | }
98 | }
99 |
100 |
101 | /**
102 | * Accept the direct connection and start the process of obtaining media.
103 | *
104 | * @param context An application context with which to access system resources
105 | */
106 | public void accept(Context context) {
107 | if (null != callReference) {
108 | RespokeCall call = callReference.get();
109 | if (null != call) {
110 | call.directConnectionDidAccept(context);
111 | }
112 | }
113 | }
114 |
115 |
116 | /**
117 | * Indicate whether a datachannel is being setup or is in progress.
118 | *
119 | * @return True the direct connection is active, false otherwise
120 | */
121 | public boolean isActive() {
122 | return ((null != dataChannel) && (dataChannel.state() == DataChannel.State.OPEN));
123 | }
124 |
125 |
126 | /**
127 | * Get the call object associated with this direct connection
128 | *
129 | * @return The call instance
130 | */
131 | public RespokeCall getCall() {
132 | if (null != callReference) {
133 | return callReference.get();
134 | } else {
135 | return null;
136 | }
137 | }
138 |
139 |
140 | /**
141 | * Send a message to the remote client through the direct connection.
142 | *
143 | * @param message The message to send
144 | * @param completionListener A listener to receive a notification on the success of the asynchronous operation
145 | */
146 | public void sendMessage(String message, final Respoke.TaskCompletionListener completionListener) {
147 | if (isActive()) {
148 | JSONObject jsonMessage = new JSONObject();
149 | try {
150 | jsonMessage.put("message", message);
151 | byte[] rawMessage = jsonMessage.toString().getBytes(Charset.forName("UTF-8"));
152 | ByteBuffer directData = ByteBuffer.allocateDirect(rawMessage.length);
153 | directData.put(rawMessage);
154 | directData.flip();
155 | DataChannel.Buffer data = new DataChannel.Buffer(directData, false);
156 |
157 | if (dataChannel.send(data)) {
158 | Respoke.postTaskSuccess(completionListener);
159 | } else {
160 | Respoke.postTaskError(completionListener, "Error sending message");
161 | }
162 | } catch (JSONException e) {
163 | Respoke.postTaskError(completionListener, "Unable to encode message to JSON");
164 | }
165 | } else {
166 | Respoke.postTaskError(completionListener, "DataChannel not in an open state");
167 | }
168 | }
169 |
170 |
171 | /**
172 | * Establish a new direct connection instance with the peer connection for the call. This is used internally to the SDK and should not be called directly by your client application.
173 | */
174 | public void createDataChannel() {
175 | if (null != callReference) {
176 | RespokeCall call = callReference.get();
177 | if (null != call) {
178 | PeerConnection peerConnection = call.getPeerConnection();
179 | dataChannel = peerConnection.createDataChannel("respokeDataChannel", new DataChannel.Init());
180 | dataChannel.registerObserver(this);
181 | }
182 | }
183 | }
184 |
185 |
186 | /**
187 | * Notify the direct connection instance that the peer connection has opened the specified data channel
188 | *
189 | * @param newDataChannel The DataChannel that has opened
190 | */
191 | public void peerConnectionDidOpenDataChannel(DataChannel newDataChannel) {
192 | if (null != dataChannel) {
193 | // Replacing the previous connection, so disable observer messages from the old instance
194 | dataChannel.unregisterObserver();
195 | } else {
196 | new Handler(Looper.getMainLooper()).post(new Runnable() {
197 | public void run() {
198 | if (null != listenerReference) {
199 | Listener listener = listenerReference.get();
200 | if (null != listener) {
201 | listener.onStart(RespokeDirectConnection.this);
202 | }
203 | }
204 | }
205 | });
206 | }
207 |
208 | dataChannel = newDataChannel;
209 | newDataChannel.registerObserver(this);
210 | }
211 |
212 |
213 | // org.webrtc.DataChannel.Observer methods
214 |
215 |
216 | public void onStateChange() {
217 | switch (dataChannel.state()) {
218 | case CONNECTING:
219 | break;
220 |
221 | case OPEN: {
222 | if (null != callReference) {
223 | RespokeCall call = callReference.get();
224 | if (null != call) {
225 | call.directConnectionDidOpen(this);
226 | }
227 | }
228 |
229 | new Handler(Looper.getMainLooper()).post(new Runnable() {
230 | public void run() {
231 | if (null != listenerReference) {
232 | Listener listener = listenerReference.get();
233 | if (null != listener) {
234 | listener.onOpen(RespokeDirectConnection.this);
235 | }
236 | }
237 | }
238 | });
239 | }
240 | break;
241 |
242 | case CLOSING:
243 | break;
244 |
245 | case CLOSED: {
246 | if (null != callReference) {
247 | RespokeCall call = callReference.get();
248 | if (null != call) {
249 | call.directConnectionDidClose(this);
250 | }
251 | }
252 |
253 | new Handler(Looper.getMainLooper()).post(new Runnable() {
254 | public void run() {
255 | if (null != listenerReference) {
256 | Listener listener = listenerReference.get();
257 | if (null != listener) {
258 | listener.onClose(RespokeDirectConnection.this);
259 | }
260 | }
261 | }
262 | });
263 | }
264 | break;
265 | }
266 | }
267 |
268 |
269 | public void onMessage(org.webrtc.DataChannel.Buffer buffer) {
270 | if (buffer.binary) {
271 | // TODO
272 | } else {
273 | Charset charset = Charset.forName("UTF-8");
274 | CharsetDecoder decoder = charset.newDecoder();
275 | try {
276 | String message = decoder.decode( buffer.data ).toString();
277 |
278 | try {
279 | JSONObject jsonMessage = new JSONObject(message);
280 | final String messageText = jsonMessage.getString("message");
281 |
282 | if (null != messageText) {
283 | new Handler(Looper.getMainLooper()).post(new Runnable() {
284 | public void run() {
285 | if (null != listenerReference) {
286 | Listener listener = listenerReference.get();
287 | if (null != listener) {
288 | listener.onMessage(messageText, RespokeDirectConnection.this);
289 | }
290 | }
291 | }
292 | });
293 | }
294 | } catch (JSONException e) {
295 | // If it is not valid json, ignore the message
296 | }
297 | } catch (CharacterCodingException e) {
298 | // If the message can not be decoded, ignore it
299 | }
300 | }
301 | }
302 |
303 |
304 | }
305 |
--------------------------------------------------------------------------------
/respokeSDK/src/main/java/com/digium/respokesdk/RespokeEndpoint.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdk;
12 |
13 | import android.content.Context;
14 | import android.opengl.GLSurfaceView;
15 | import android.os.Handler;
16 | import android.os.Looper;
17 | import android.util.Log;
18 |
19 | import org.json.JSONArray;
20 | import org.json.JSONException;
21 | import org.json.JSONObject;
22 |
23 | import java.lang.ref.WeakReference;
24 | import java.util.ArrayList;
25 | import java.util.Date;
26 | import java.util.Iterator;
27 |
28 | /**
29 | * Represents remote Endpoints. Endpoints are users of this application that are not the one logged into this
30 | * instance of the application. An Endpoint could be logged in from multiple other instances of this app, each of
31 | * which is represented by a Connection. The client can interact with endpoints by calling them or
32 | * sending them messages. An endpoint can be a person using an app from a browser or a script using the APIs on
33 | * a server.
34 | */
35 | public class RespokeEndpoint {
36 |
37 | private WeakReference listenerReference;
38 | private String endpointID;
39 | public ArrayList connections;
40 | private RespokeSignalingChannel signalingChannel;
41 | public Object presence;
42 | private WeakReference directConnectionReference;
43 | private WeakReference clientReference;
44 |
45 |
46 | /**
47 | * A listener interface to notify the receiver of events occurring with the endpoint
48 | */
49 | public interface Listener {
50 |
51 | /**
52 | * Handle messages sent to the logged-in user from this one Endpoint.
53 | *
54 | * @param message The message
55 | * @param timestamp The timestamp of the message
56 | * @param endpoint The remote endpoint that sent the message
57 | * @param didSend True if the specified endpoint sent the message, False if it received the message
58 | */
59 | public void onMessage(String message, Date timestamp, RespokeEndpoint endpoint, boolean didSend);
60 |
61 |
62 | /**
63 | * A notification that the presence for an endpoint has changed
64 | *
65 | * @param presence The new presence
66 | * @param sender The endpoint
67 | */
68 | public void onPresence(Object presence, RespokeEndpoint sender);
69 |
70 | }
71 |
72 |
73 | /**
74 | * The constructor for this class
75 | *
76 | * @param channel The signaling channel managing communications with this endpoint
77 | * @param newEndpointID The ID for this endpoint
78 | * @param client The client to which this endpoint instance belongs
79 | */
80 | public RespokeEndpoint(RespokeSignalingChannel channel, String newEndpointID, RespokeClient client) {
81 | endpointID = newEndpointID;
82 | signalingChannel = channel;
83 | connections = new ArrayList();
84 | clientReference = new WeakReference(client);
85 | }
86 |
87 |
88 | /**
89 | * Set a receiver for the Listener interface
90 | *
91 | * @param listener The new receiver for events from the Listener interface for this endpoint instance
92 | */
93 | public void setListener(Listener listener) {
94 | listenerReference = new WeakReference(listener);
95 | }
96 |
97 |
98 | /**
99 | * Send a message to the endpoint through the infrastructure.
100 | *
101 | * @param message The message to send
102 | * @param push A flag indicating if a push notification should be sent for this message
103 | * @param ccSelf A flag indicating if the message should be copied to other devices the client might be logged into
104 | * @param completionListener A listener to receive a notification on the success of the asynchronous operation
105 | */
106 | public void sendMessage(String message, boolean push, boolean ccSelf, final Respoke.TaskCompletionListener completionListener) {
107 | if ((null != signalingChannel) && (signalingChannel.connected)) {
108 | try {
109 | JSONObject data = new JSONObject();
110 | data.put("to", endpointID);
111 | data.put("message", message);
112 | data.put("push", push);
113 | data.put("ccSelf", ccSelf);
114 |
115 | signalingChannel.sendRESTMessage("post", "/v1/messages", data, new RespokeSignalingChannel.RESTListener() {
116 | @Override
117 | public void onSuccess(Object response) {
118 | Respoke.postTaskSuccess(completionListener);
119 | }
120 |
121 | @Override
122 | public void onError(final String errorMessage) {
123 | Respoke.postTaskError(completionListener, errorMessage);
124 | }
125 | });
126 | } catch (JSONException e) {
127 | Respoke.postTaskError(completionListener, "Error encoding message");
128 | }
129 | } else {
130 | Respoke.postTaskError(completionListener, "Can't complete request when not connected. Please reconnect!");
131 | }
132 | }
133 |
134 |
135 | /**
136 | * Create a new call with audio and optionally video.
137 | *
138 | * @param callListener A listener to receive notifications of call related events
139 | * @param context An application context with which to access system resources
140 | * @param glView A GLSurfaceView into which video from the call should be rendered, or null if the call is audio only
141 | * @param audioOnly Specify true for an audio-only call
142 | *
143 | * @return A new RespokeCall instance
144 | */
145 | public RespokeCall startCall(RespokeCall.Listener callListener, Context context, GLSurfaceView glView, boolean audioOnly) {
146 | RespokeCall call = null;
147 |
148 | if ((null != signalingChannel) && (signalingChannel.connected)) {
149 | call = new RespokeCall(signalingChannel, this, false);
150 | call.setListener(callListener);
151 |
152 | call.startCall(context, glView, audioOnly);
153 | }
154 |
155 | return call;
156 | }
157 |
158 |
159 | /**
160 | * Get the endpoint's ID
161 | *
162 | * @return The ID
163 | */
164 | public String getEndpointID() {
165 | return endpointID;
166 | }
167 |
168 |
169 | /**
170 | * Returns a connection with the specified ID, and optionally creates one if it does not exist
171 | *
172 | * @param connectionID The ID of the connection
173 | * @param skipCreate Whether or not to create a new connection if it is not found
174 | *
175 | * @return The connection that matches the specified ID, or null if not found and skipCreate is true
176 | */
177 | public RespokeConnection getConnection(String connectionID, boolean skipCreate) {
178 | RespokeConnection connection = null;
179 |
180 | for (RespokeConnection eachConnection : connections) {
181 | if (eachConnection.connectionID.equals(connectionID)) {
182 | connection = eachConnection;
183 | break;
184 | }
185 | }
186 |
187 | if ((null == connection) && !skipCreate) {
188 | connection = new RespokeConnection(connectionID, this);
189 | connections.add(connection);
190 | }
191 |
192 | return connection;
193 | }
194 |
195 |
196 | /**
197 | * Get an array of connections associated with this endpoint
198 | *
199 | * @return The array of connections
200 | */
201 | public ArrayList getConnections() {
202 | return connections;
203 | }
204 |
205 |
206 | /**
207 | * Process a received message. This is used internally to the SDK and should not be called directly by your client application.
208 | *
209 | * @param message The body of the message
210 | * @param timestamp The message timestamp
211 | */
212 | public void didReceiveMessage(final String message, final Date timestamp) {
213 | new Handler(Looper.getMainLooper()).post(new Runnable() {
214 | @Override
215 | public void run() {
216 | if (null != listenerReference) {
217 | Listener listener = listenerReference.get();
218 | if (null != listener) {
219 | listener.onMessage(message, timestamp, RespokeEndpoint.this, false);
220 | }
221 | }
222 | }
223 | });
224 | }
225 |
226 |
227 | /**
228 | * Process a sent message. This is used internally to the SDK and should not be called directly by your client application.
229 | *
230 | * @param message The body of the message
231 | * @param timestamp The message timestamp
232 | */
233 | public void didSendMessage(final String message, final Date timestamp) {
234 | new Handler(Looper.getMainLooper()).post(new Runnable() {
235 | @Override
236 | public void run() {
237 | if (null != listenerReference) {
238 | Listener listener = listenerReference.get();
239 | if (null != listener) {
240 | listener.onMessage(message, timestamp, RespokeEndpoint.this, true);
241 | }
242 | }
243 | }
244 | });
245 | }
246 |
247 |
248 | /**
249 | * Find the presence out of all known connections with the highest priority (most availability)
250 | * and set it as the endpoint's resolved presence.
251 | */
252 | public void resolvePresence() {
253 | ArrayList list = new ArrayList();
254 |
255 | for (RespokeConnection eachConnection : connections) {
256 | Object connectionPresence = eachConnection.presence;
257 |
258 | if (null != connectionPresence) {
259 | list.add(connectionPresence);
260 | }
261 | }
262 |
263 | RespokeClient client = null;
264 | RespokeClient.ResolvePresenceListener resolveListener = null;
265 | if (null != clientReference) {
266 | client = clientReference.get();
267 |
268 | if (client != null) {
269 | resolveListener = client.getResolvePresenceListener();
270 | }
271 | }
272 |
273 | if (null != resolveListener) {
274 | presence = resolveListener.resolvePresence(list);
275 | } else {
276 | ArrayList options = new ArrayList();
277 | options.add("chat");
278 | options.add("available");
279 | options.add("away");
280 | options.add("dnd");
281 | options.add("xa");
282 | options.add("unavailable");
283 |
284 | String newPresence = null;
285 | for (String eachOption : options) {
286 | for (Object eachObject : list) {
287 | if (eachObject instanceof String) {
288 | String eachObjectString = (String) eachObject;
289 |
290 | if (eachObjectString.toLowerCase().equals(eachOption)) {
291 | newPresence = eachOption;
292 | }
293 | }
294 | }
295 |
296 | if (null != newPresence) {
297 | break;
298 | }
299 | }
300 |
301 | if (null == newPresence) {
302 | newPresence = "unavailable";
303 | }
304 |
305 | presence = newPresence;
306 | }
307 |
308 | new Handler(Looper.getMainLooper()).post(new Runnable() {
309 | @Override
310 | public void run() {
311 | if (null != listenerReference) {
312 | Listener listener = listenerReference.get();
313 | if (null != listener) {
314 | listener.onPresence(presence, RespokeEndpoint.this);
315 | }
316 | }
317 | }
318 | });
319 | }
320 |
321 |
322 | /**
323 | * Get the active direct connection with this endpoint (if any)
324 | *
325 | * @return The active direct connection instance, or null otherwise
326 | */
327 | public RespokeDirectConnection directConnection() {
328 | if (null != directConnectionReference) {
329 | return directConnectionReference.get();
330 | } else {
331 | return null;
332 | }
333 | }
334 |
335 |
336 | /**
337 | * Associate a direct connection object with this endpoint. This method is used internally by the SDK should not be called by your client application.
338 | *
339 | * @param newDirectConnection The direct connection to associate
340 | */
341 | public void setDirectConnection(RespokeDirectConnection newDirectConnection) {
342 | if (null != newDirectConnection) {
343 | directConnectionReference = new WeakReference(newDirectConnection);
344 | } else {
345 | directConnectionReference = null;
346 | }
347 | }
348 |
349 |
350 | /**
351 | * Create a new DirectConnection. This method creates a new Call as well, attaching this DirectConnection to
352 | * it for the purposes of creating a peer-to-peer link for sending data such as messages to the other endpoint.
353 | * Information sent through a DirectConnection is not handled by the cloud infrastructure.
354 | *
355 | * @return The DirectConnection which can be used to send data and messages directly to the other endpoint.
356 | */
357 | public RespokeDirectConnection startDirectConnection() {
358 | // The constructor will call the setDirectConnection method on this endpoint instance with a reference to the new RespokeDirectConnection object
359 | RespokeCall call = new RespokeCall(signalingChannel, this, true);
360 | call.startCall(null, null, false);
361 |
362 | return directConnection();
363 | }
364 | }
365 |
--------------------------------------------------------------------------------
/respokeSDK/src/main/java/com/digium/respokesdk/RespokeGroup.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdk;
12 |
13 | import android.os.Handler;
14 | import android.os.Looper;
15 |
16 | import org.json.JSONArray;
17 | import org.json.JSONException;
18 | import org.json.JSONObject;
19 |
20 | import java.lang.ref.WeakReference;
21 | import java.util.ArrayList;
22 | import java.util.Date;
23 |
24 |
25 | /**
26 | * A group, representing a collection of connections and the method by which to communicate with them.
27 | */
28 | public class RespokeGroup {
29 |
30 | private WeakReference listenerReference;
31 | private String groupID; ///< The ID of this group
32 | private WeakReference clientReference; ///< The client managing this group
33 | private RespokeSignalingChannel signalingChannel; ///< The signaling channel to use
34 | private ArrayList members; ///< An array of the members of this group
35 | private boolean joined; ///< Indicates if the client is a member of this group
36 |
37 |
38 | /**
39 | * A listener interface to notify the receiver of events occurring with the group
40 | */
41 | public interface Listener {
42 |
43 |
44 | /**
45 | * Receive a notification that an connection has joined this group.
46 | *
47 | * @param connection The RespokeConnection that joined the group
48 | * @param sender The RespokeGroup that the connection has joined
49 | */
50 | void onJoin(RespokeConnection connection, RespokeGroup sender);
51 |
52 |
53 | /**
54 | * Receive a notification that an connection has left this group.
55 | *
56 | * @param connection The RespokeConnection that left the group
57 | * @param sender The RespokeGroup that the connection has left
58 | */
59 | void onLeave(RespokeConnection connection, RespokeGroup sender);
60 |
61 |
62 | /**
63 | * Receive a notification that a group message has been received
64 | *
65 | * @param message The body of the message
66 | * @param endpoint The endpoint that sent the message
67 | * @param sender The group that received the message
68 | * @param timestamp The timestamp of when the message was sent.
69 | */
70 | void onGroupMessage(String message, RespokeEndpoint endpoint, RespokeGroup sender, Date timestamp);
71 |
72 |
73 | }
74 |
75 |
76 | /**
77 | * A listener interface to receive a notification that the task to get the list of group members has completed
78 | */
79 | public interface GetGroupMembersCompletionListener {
80 |
81 | /**
82 | * Receive an array of the group members asynchronously
83 | *
84 | * @param memberArray An array of the connections that are a member of this group
85 | */
86 | void onSuccess(ArrayList memberArray);
87 |
88 |
89 | /**
90 | * Receive a notification that the asynchronous operation failed
91 | *
92 | * @param errorMessage A human-readable description of the error that was encountered
93 | */
94 | void onError(String errorMessage);
95 | }
96 |
97 | /**
98 | * The constructor for this class
99 | *
100 | * @param newGroupID The ID for this group
101 | * @param channel The signaling channel managing communications with this group
102 | * @param newClient The client to which this group instance belongs
103 | * @param isJoined Whether the group has already been joined
104 | */
105 | public RespokeGroup(String newGroupID, RespokeSignalingChannel channel, RespokeClient newClient,
106 | Boolean isJoined) {
107 | groupID = newGroupID;
108 | signalingChannel = channel;
109 | clientReference = new WeakReference(newClient);
110 | members = new ArrayList();
111 | joined = isJoined;
112 | }
113 |
114 | public RespokeGroup(String newGroupID, RespokeSignalingChannel channel, RespokeClient newClient) {
115 | this(newGroupID, channel, newClient, true);
116 | }
117 |
118 | /**
119 | * Set a receiver for the Listener interface
120 | *
121 | * @param listener The new receiver for events from the Listener interface for this group instance
122 | */
123 | public void setListener(Listener listener) {
124 | listenerReference = new WeakReference(listener);
125 | }
126 |
127 |
128 | /**
129 | * Get an array containing the members of the group.
130 | *
131 | * @param completionListener A listener to receive a notification on the success of the asynchronous operation
132 | **/
133 | public void getMembers(final GetGroupMembersCompletionListener completionListener) {
134 | if (isJoined()) {
135 | if ((null != groupID) && (groupID.length() > 0)) {
136 | String urlEndpoint = "/v1/channels/" + groupID + "/subscribers/";
137 |
138 | signalingChannel.sendRESTMessage("get", urlEndpoint, null, new RespokeSignalingChannel.RESTListener() {
139 | @Override
140 | public void onSuccess(Object response) {
141 | JSONArray responseArray = null;
142 |
143 | if (response != null) {
144 | if (response instanceof JSONArray) {
145 | responseArray = (JSONArray) response;
146 | } else if (response instanceof String) {
147 | try {
148 | responseArray = new JSONArray((String) response);
149 | } catch (JSONException e) {
150 | // An exception will trigger the error handler
151 | }
152 | }
153 | }
154 |
155 | if (null != responseArray) {
156 | final ArrayList nameList = new ArrayList();
157 | RespokeClient client = clientReference.get();
158 | if (null != client) {
159 | for (int ii = 0; ii < responseArray.length(); ii++) {
160 | try {
161 | JSONObject eachEntry = (JSONObject) responseArray.get(ii);
162 | String newEndpointID = eachEntry.getString("endpointId");
163 | String newConnectionID = eachEntry.getString("connectionId");
164 |
165 | // Do not include ourselves in this list
166 | if (!newEndpointID.equals(client.getEndpointID())) {
167 | // Get the existing instance for this connection, or create a new one if necessary
168 | RespokeConnection connection = client.getConnection(newConnectionID, newEndpointID, false);
169 |
170 | if (null != connection) {
171 | nameList.add(connection);
172 | }
173 | }
174 | } catch (JSONException e) {
175 | // Skip unintelligible records
176 | }
177 | }
178 | }
179 |
180 | // If certain connections present in the members array prior to this method are somehow no longer in the list received from the server, it's assumed a pending onLeave message will handle flushing it out of the client cache after this method completes
181 | members.clear();
182 | members.addAll(nameList);
183 |
184 | new Handler(Looper.getMainLooper()).post(new Runnable() {
185 | @Override
186 | public void run() {
187 | if (null != completionListener) {
188 | completionListener.onSuccess(nameList);
189 | }
190 | }
191 | });
192 | } else {
193 | postGetGroupMembersError(completionListener, "Invalid response from server");
194 | }
195 | }
196 |
197 | @Override
198 | public void onError(final String errorMessage) {
199 | postGetGroupMembersError(completionListener, errorMessage);
200 | }
201 | });
202 | } else {
203 | postGetGroupMembersError(completionListener, "Group name must be specified");
204 | }
205 | } else {
206 | postGetGroupMembersError(completionListener, "Not a member of this group anymore.");
207 | }
208 | }
209 |
210 |
211 | /**
212 | * Join this group
213 | *
214 | * @param completionListener A listener to receive a notification upon completion of this
215 | * async operation
216 | */
217 | public void join(final Respoke.TaskCompletionListener completionListener) {
218 | if (!isConnected()) {
219 | Respoke.postTaskError(completionListener, "Can't complete request when not connected. " +
220 | "Please reconnect!");
221 | return;
222 | }
223 |
224 | if ((groupID == null) || (groupID.length() == 0)) {
225 | Respoke.postTaskError(completionListener, "Group name must be specified");
226 | return;
227 | }
228 |
229 | String urlEndpoint = String.format("/v1/groups/%s", groupID);
230 | signalingChannel.sendRESTMessage("post", urlEndpoint, null, new RespokeSignalingChannel.RESTListener() {
231 | @Override
232 | public void onSuccess(Object response) {
233 | joined = true;
234 | Respoke.postTaskSuccess(completionListener);
235 | }
236 |
237 | @Override
238 | public void onError(final String errorMessage) {
239 | Respoke.postTaskError(completionListener, errorMessage);
240 | }
241 | });
242 | }
243 |
244 |
245 | /**
246 | * Leave this group
247 | *
248 | * @param completionListener A listener to receive a notification on the success of the asynchronous operation
249 | **/
250 | public void leave(final Respoke.TaskCompletionListener completionListener) {
251 | if (isJoined()) {
252 | if ((null != groupID) && (groupID.length() > 0)) {
253 | String urlEndpoint = "/v1/groups";
254 |
255 | JSONArray groupList = new JSONArray();
256 | groupList.put(groupID);
257 |
258 | JSONObject data = new JSONObject();
259 | try {
260 | data.put("groups", groupList);
261 |
262 | signalingChannel.sendRESTMessage("delete", urlEndpoint, data, new RespokeSignalingChannel.RESTListener() {
263 | @Override
264 | public void onSuccess(Object response) {
265 | joined = false;
266 |
267 | Respoke.postTaskSuccess(completionListener);
268 | }
269 |
270 | @Override
271 | public void onError(final String errorMessage) {
272 | Respoke.postTaskError(completionListener, errorMessage);
273 | }
274 | });
275 | } catch (JSONException e) {
276 | Respoke.postTaskError(completionListener, "Error encoding group list to json");
277 | }
278 | } else {
279 | Respoke.postTaskError(completionListener, "Group name must be specified");
280 | }
281 | } else {
282 | Respoke.postTaskError(completionListener, "Not a member of this group anymore.");
283 | }
284 | }
285 |
286 |
287 | /**
288 | * Return true if the local client is a member of this group and false if not.
289 | *
290 | * @return The membership status
291 | */
292 | public boolean isJoined() {
293 | return joined && isConnected();
294 | }
295 |
296 | public boolean isConnected() {
297 | return (null != signalingChannel) && signalingChannel.connected;
298 | }
299 |
300 | /**
301 | * Get the ID for this group
302 | *
303 | * @return The group's ID
304 | */
305 | public String getGroupID() {
306 | return groupID;
307 | }
308 |
309 |
310 | /**
311 | * Send a message to the entire group.
312 | *
313 | * @param message The message to send
314 | * @param push A flag indicating if a push notification should be sent for this message
315 | * @param persist A flag indicating if history should be maintained for this message.
316 | * @param completionListener A listener to receive a notification on the success of the asynchronous operation
317 | **/
318 | public void sendMessage(String message, boolean push, boolean persist,
319 | final Respoke.TaskCompletionListener completionListener) {
320 | if (isJoined()) {
321 | if ((null != groupID) && (groupID.length() > 0)) {
322 | RespokeClient client = clientReference.get();
323 | if (null != client) {
324 |
325 | JSONObject data = new JSONObject();
326 |
327 | try {
328 | data.put("endpointId", client.getEndpointID());
329 | data.put("message", message);
330 | data.put("push", push);
331 | data.put("persist", persist);
332 | } catch (JSONException e) {
333 | Respoke.postTaskError(completionListener, "Unable to encode message");
334 | return;
335 | }
336 |
337 | String urlEndpoint = "/v1/channels/" + groupID + "/publish/";
338 | signalingChannel.sendRESTMessage("post", urlEndpoint, data, new RespokeSignalingChannel.RESTListener() {
339 | @Override
340 | public void onSuccess(Object response) {
341 | Respoke.postTaskSuccess(completionListener);
342 | }
343 |
344 | @Override
345 | public void onError(final String errorMessage) {
346 | Respoke.postTaskError(completionListener, errorMessage);
347 | }
348 | });
349 | } else {
350 | Respoke.postTaskError(completionListener, "There was an internal error processing this request.");
351 | }
352 | } else {
353 | Respoke.postTaskError(completionListener, "Group name must be specified");
354 | }
355 | } else {
356 | Respoke.postTaskError(completionListener, "Not a member of this group anymore.");
357 | }
358 | }
359 |
360 | public void sendMessage(String message, boolean push, final Respoke.TaskCompletionListener completionListener) {
361 | sendMessage(message, push, false, completionListener);
362 | }
363 |
364 |
365 | /**
366 | * Notify the group that a connection has joined. This is used internally to the SDK and should not be called directly by your client application.
367 | *
368 | * @param connection The connection that has joined the group
369 | */
370 | public void connectionDidJoin(final RespokeConnection connection) {
371 | members.add(connection);
372 |
373 | new Handler(Looper.getMainLooper()).post(new Runnable() {
374 | @Override
375 | public void run() {
376 | Listener listener = listenerReference.get();
377 | if (null != listener) {
378 | listener.onJoin(connection, RespokeGroup.this);
379 | }
380 | }
381 | });
382 | }
383 |
384 |
385 | /**
386 | * Notify the group that a connection has left. This is used internally to the SDK and should not be called directly by your client application.
387 | *
388 | * @param connection The connection that has left the group
389 | */
390 | public void connectionDidLeave(final RespokeConnection connection) {
391 | members.remove(connection);
392 |
393 | new Handler(Looper.getMainLooper()).post(new Runnable() {
394 | @Override
395 | public void run() {
396 | Listener listener = listenerReference.get();
397 | if (null != listener) {
398 | listener.onLeave(connection, RespokeGroup.this);
399 | }
400 | }
401 | });
402 | }
403 |
404 |
405 | /**
406 | * Notify the group that a group message was received. This is used internally to the SDK and should not be called directly by your client application.
407 | *
408 | * @param message The body of the message
409 | * @param endpoint The endpoint that sent the message
410 | * @param timestamp The message timestamp
411 | */
412 | public void didReceiveMessage(final String message, final RespokeEndpoint endpoint, final Date timestamp) {
413 | new Handler(Looper.getMainLooper()).post(new Runnable() {
414 | @Override
415 | public void run() {
416 | Listener listener = listenerReference.get();
417 | if (null != listener) {
418 | listener.onGroupMessage(message, endpoint, RespokeGroup.this, timestamp);
419 | }
420 | }
421 | });
422 | }
423 |
424 |
425 | //** Private methods
426 |
427 |
428 | /**
429 | * A convenience method for posting errors to a GetGroupMembersCompletionListener
430 | *
431 | * @param completionListener The listener to notify
432 | * @param errorMessage The human-readable error message that occurred
433 | */
434 | private void postGetGroupMembersError(final GetGroupMembersCompletionListener completionListener, final String errorMessage) {
435 | new Handler(Looper.getMainLooper()).post(new Runnable() {
436 | @Override
437 | public void run() {
438 | if (null != completionListener) {
439 | completionListener.onError(errorMessage);
440 | }
441 | }
442 | });
443 | }
444 |
445 |
446 | }
447 |
--------------------------------------------------------------------------------
/respokeSDK/src/main/java/com/digium/respokesdk/RespokeGroupMessage.java:
--------------------------------------------------------------------------------
1 | package com.digium.respokesdk;
2 |
3 | import java.util.Date;
4 |
5 | public class RespokeGroupMessage {
6 | public String message;
7 | public RespokeGroup group;
8 | public RespokeEndpoint endpoint;
9 | public Date timestamp;
10 |
11 | public RespokeGroupMessage(String message, RespokeGroup group, RespokeEndpoint endpoint,
12 | Date timestamp) {
13 | this.message = message;
14 | this.endpoint = endpoint;
15 | this.group = group;
16 | this.timestamp = timestamp;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/respokeSDK/src/main/java/com/digium/respokesdk/RespokeWorkerThread.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdk;
12 |
13 | import android.os.Handler;
14 | import android.os.HandlerThread;
15 |
16 | /**
17 | * Implements a worker thread for queueing and processing socket transactions with the Respoke service
18 | */
19 | public class RespokeWorkerThread extends HandlerThread {
20 |
21 | private Handler workerHandler;
22 |
23 |
24 | public RespokeWorkerThread(String name) {
25 | super(name);
26 | }
27 |
28 |
29 | public void postTask(Runnable task){
30 | workerHandler.post(task);
31 | }
32 |
33 |
34 | public void postTaskDelayed(Runnable task, long delayMillis){
35 | workerHandler.postDelayed(task, delayMillis);
36 | }
37 |
38 |
39 | public void prepareHandler(){
40 | workerHandler = new Handler(getLooper());
41 | }
42 |
43 |
44 | public void cancelAllTasks() {
45 | // Cancel all pending tasks and callbacks, but leave the thread ready to run new tasks
46 | workerHandler.removeCallbacksAndMessages(null);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/respokeSDK/src/main/java/com/digium/respokesdk/RestAPI/APIDoOpen.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdk.RestAPI;
12 |
13 | import android.content.Context;
14 |
15 | import org.json.JSONException;
16 |
17 |
18 | public class APIDoOpen extends APITransaction {
19 |
20 | public String tokenID;
21 | public String appToken;
22 |
23 | public APIDoOpen(Context context, String baseURL) {
24 | super(context, baseURL + "/v1/session-tokens");
25 | }
26 |
27 |
28 | public void go() {
29 | params = "tokenId=" + tokenID;
30 |
31 | super.go();
32 | }
33 |
34 |
35 | @Override
36 | public void transactionComplete() {
37 | if (success) {
38 | try {
39 | appToken = jsonResult.getString("token");
40 | }
41 | catch (JSONException e) {
42 | success = false;
43 | errorMessage = "Unexpected response from server";
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/respokeSDK/src/main/java/com/digium/respokesdk/RestAPI/APIGetToken.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdk.RestAPI;
12 |
13 | import android.content.Context;
14 |
15 | import org.json.JSONException;
16 |
17 |
18 | public class APIGetToken extends APITransaction {
19 |
20 | private static final String DEFAULT_TTL = "21600";
21 |
22 | public String appID;
23 | public String endpointID;
24 | public String token;
25 |
26 | public APIGetToken(Context context, String baseURL) {
27 | super(context, baseURL + "/v1/tokens");
28 | }
29 |
30 |
31 | public void go() {
32 | params = "appId=" + appID + "&endpointId=" + endpointID + "&ttl=" + DEFAULT_TTL;
33 |
34 | super.go();
35 | }
36 |
37 |
38 | @Override
39 | public void transactionComplete() {
40 | if (success) {
41 | try {
42 | token = jsonResult.getString("tokenId");
43 | }
44 | catch (JSONException e) {
45 | success = false;
46 | errorMessage = "Unexpected response from server";
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/respokeSDK/src/main/java/com/digium/respokesdk/RestAPI/APITransaction.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdk.RestAPI;
12 |
13 | import java.io.BufferedReader;
14 | import java.io.DataOutputStream;
15 | import java.io.IOException;
16 | import java.io.InputStream;
17 | import java.io.InputStreamReader;
18 | import java.io.UnsupportedEncodingException;
19 | import java.net.CookieHandler;
20 | import java.net.CookieManager;
21 | import java.net.CookiePolicy;
22 | import java.net.HttpURLConnection;
23 | import java.net.URI;
24 | import java.net.URISyntaxException;
25 | import java.net.URL;
26 |
27 | import org.json.JSONException;
28 | import org.json.JSONObject;
29 |
30 | import android.content.Context;
31 | import android.os.AsyncTask;
32 | import android.util.Log;
33 |
34 | import com.digium.respokesdk.BuildConfig;
35 |
36 |
37 | public class APITransaction {
38 |
39 | private static final String TAG = "ApiTransaction";
40 | public static final String RESPOKE_BASE_URL = "https://api.respoke.io";
41 |
42 | /**
43 | * Rejects a message if the body size is greater than this. It is enforced server side, so changing this
44 | * won't make the bodySizeLimit any bigger, this just gives you a sensible error if it's too big.
45 | */
46 | public static final long bodySizeLimit = 20000;
47 |
48 | public boolean abort;
49 | public boolean success;
50 | public String errorMessage;
51 | public JSONObject jsonResult;
52 | public String baseURL;
53 | public String contentType;
54 | Context context;
55 |
56 | private HttpURLConnection connection;
57 | protected String httpMethod;
58 | protected String params;
59 | protected int serverResponseCode;
60 | private AsyncTransaction asyncTrans;
61 |
62 | public static String getSDKHeader() {
63 | String sdkTitle = "Respoke-Android";
64 | String sdkVersion = BuildConfig.VERSION_NAME;
65 | String androidVersion = android.os.Build.VERSION.RELEASE;
66 |
67 | if (!sdkVersion.equals("")) {
68 | sdkTitle = sdkTitle + "/" + sdkVersion;
69 | }
70 |
71 | return String.format("%s (Android %s)", sdkTitle, androidVersion);
72 | }
73 |
74 | public APITransaction(Context context, String baseURL) {
75 | this.context = context;
76 | abort = false;
77 | httpMethod = "POST";
78 | params = null;
79 | this.baseURL = baseURL;
80 | contentType = "application/x-www-form-urlencoded";
81 | }
82 |
83 |
84 | public void go() {
85 | asyncTrans = new AsyncTransaction();
86 | asyncTrans.execute(this.httpMethod);
87 | }
88 |
89 |
90 | public void transactionComplete() {
91 | // This method is overridden by child classes
92 | }
93 |
94 |
95 | public void cancel() {
96 | abort = true;
97 | if (asyncTrans != null) {
98 | asyncTrans.cancel();
99 | }
100 | }
101 |
102 |
103 | private class AsyncTransaction extends AsyncTask {
104 |
105 | public void cancel() {
106 | if (connection != null) {
107 | Log.e(TAG, "connection cancelled!");
108 | connection.disconnect();
109 | }
110 | this.cancel(true);
111 | }
112 |
113 | @Override
114 | protected Object doInBackground(String... args) {
115 |
116 | String httpMethod = args[0];
117 |
118 | // clear any previous received data
119 | jsonResult = null;
120 |
121 | try {
122 | if (params.getBytes("UTF-8").length <= bodySizeLimit) {
123 | try {
124 | //accept no cookies
125 | CookieManager cookieManager = new CookieManager();
126 | cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_NONE);
127 | CookieHandler.setDefault(cookieManager);
128 |
129 | System.setProperty("http.keepAlive", "false");
130 |
131 | URI uri = new URI(baseURL.replace(" ", "%20"));
132 | URL url = new URL(uri.toASCIIString());
133 | connection = (HttpURLConnection) url.openConnection();
134 |
135 | // Allow Inputs & Outputs
136 | connection.setRequestMethod(httpMethod);
137 | connection.setDoInput(true);
138 | connection.setUseCaches(false);
139 | if (httpMethod.equals("POST")) {
140 | connection.setDoOutput(true);
141 | }
142 |
143 | //Headers
144 | connection.setRequestProperty("Content-Type", contentType);
145 | connection.setRequestProperty("Accept", "application/xml");
146 | connection.setRequestProperty("Respoke-SDK", getSDKHeader());
147 |
148 | if (httpMethod.equals("POST")) {
149 | //open stream and start writing
150 | DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
151 | outputStream.writeBytes(params);
152 | outputStream.flush();
153 | outputStream.close();
154 | }
155 |
156 | serverResponseCode = connection.getResponseCode();
157 |
158 | if (serverResponseCode == 200) {
159 | success = true;
160 |
161 | // Parse the response data into a string
162 | String receivedString = readResponse(connection.getInputStream());
163 |
164 | if (receivedString != null) {
165 | try {
166 | // Parse the response string into JSON objects
167 | jsonResult = new JSONObject(receivedString);
168 | } catch (JSONException e) {
169 | errorMessage = "Error deserializing response";
170 | success = false;
171 | }
172 | }
173 | } else {
174 | throw new IOException(Integer.toString(serverResponseCode));
175 | }
176 |
177 | } catch (IOException e) {
178 | success = false;
179 | errorMessage = e.getLocalizedMessage();
180 |
181 | try {
182 | Log.e(TAG, "serverResponseCode = " + connection.getResponseCode());
183 | Log.e(TAG, "serverResponseMessage = " + connection.getResponseMessage());
184 | serverResponseCode = connection.getResponseCode();
185 | String serverResponseMessage = connection.getResponseMessage();
186 |
187 | if (serverResponseCode == 401) {
188 | errorMessage = "API authentication error";
189 | } else if (serverResponseCode == 429) {
190 | errorMessage = "API rate limit was exceeded";
191 | } else if (serverResponseCode == 503) {
192 | errorMessage = "Server is down for maintenance";
193 | } else if (serverResponseCode >= 400) {
194 | errorMessage = "Failed with server error = " + serverResponseCode + " message = " + serverResponseMessage;
195 | } else {
196 | errorMessage = "Unknown Error. http status = " + serverResponseCode + " message = " + serverResponseMessage;
197 | }
198 | } catch (IOException ioe) {
199 | ioe.printStackTrace();
200 | }
201 | } catch (URISyntaxException e) {
202 | Log.e(TAG, "Bad URI!");
203 | errorMessage = "An invalid server URL was specified";
204 | success = false;
205 | } catch (Exception e) {
206 | Log.e(TAG, "Unknown exception");
207 | errorMessage = "An unknown problem occurred";
208 | success = false;
209 | } finally {
210 | if (connection != null) {
211 | connection.disconnect();
212 | }
213 | }
214 | } else {
215 | errorMessage = "Request body is too big";
216 | success = false;
217 | }
218 | } catch (UnsupportedEncodingException e) {
219 | errorMessage = "Unable to encode message";
220 | success = false;
221 | }
222 |
223 | return jsonResult;
224 | }
225 |
226 |
227 | //Process after transaction is complete
228 | @Override
229 | protected void onPostExecute(Object result)
230 | {
231 | transactionComplete();
232 | }
233 |
234 |
235 | private String readResponse(InputStream stream) {
236 | String receivedString = null;
237 |
238 | try {
239 | BufferedReader br = new BufferedReader(new InputStreamReader(stream));
240 | StringBuilder sb = new StringBuilder();
241 | String line;
242 |
243 | while ((line = br.readLine()) != null) {
244 | sb.append(line).append("\n");
245 | }
246 |
247 | br.close();
248 | receivedString = sb.toString();
249 | } catch (IOException ioe) {
250 | // Do nothing
251 | }
252 |
253 | return receivedString;
254 | }
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/respokeSDK/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/respoke/respoke-sdk-android/34a15f0558d29b1f1bc8481bbc5c505e855e05ef/respokeSDK/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/respokeSDK/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/respoke/respoke-sdk-android/34a15f0558d29b1f1bc8481bbc5c505e855e05ef/respokeSDK/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/respokeSDK/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/respoke/respoke-sdk-android/34a15f0558d29b1f1bc8481bbc5c505e855e05ef/respokeSDK/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/respokeSDK/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/respoke/respoke-sdk-android/34a15f0558d29b1f1bc8481bbc5c505e855e05ef/respokeSDK/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/respokeSDK/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Respoke SDK
3 |
4 |
--------------------------------------------------------------------------------
/respokeSDKTest/android-wait-for-emulator:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Originally written by Ralf Kistner , but placed in the public domain
4 |
5 | set +e
6 |
7 | # If ANDROID_HOME is specified, use that adb; otherwise, search the path
8 | if test ${ANDROID_HOME}; then
9 | : ${ADB:=${ANDROID_HOME}/platform-tools/adb}
10 | else
11 | : ${ADB:=adb}
12 | fi
13 |
14 | bootanim=""
15 | failcounter=0
16 | timeout_in_sec=120
17 |
18 | until [[ "$bootanim" =~ "stopped" ]]; do
19 | bootanim=`${ADB} -e shell getprop init.svc.bootanim 2>&1 &`
20 | if [[ "$bootanim" =~ "device not found" || "$bootanim" =~ "device offline"
21 | || "$bootanim" =~ "running" ]]; then
22 | let "failcounter += 1"
23 | echo "Waiting for emulator to start"
24 | if [[ $failcounter -gt timeout_in_sec ]]; then
25 | echo "Timeout ($timeout_in_sec seconds) reached; failed to start emulator"
26 | exit 1
27 | fi
28 | fi
29 | sleep 1
30 | done
31 |
32 | echo "Emulator is ready"
33 |
--------------------------------------------------------------------------------
/respokeSDKTest/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion '23.0.3'
6 |
7 | defaultConfig {
8 | applicationId "com.digium.respokesdktest"
9 | minSdkVersion 15
10 | targetSdkVersion 23
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 |
15 | buildTypes {
16 | release {
17 | minifyEnabled false
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | resValue "string", "TEST_BOT_SUFFIX", System.getenv("USER")
20 | }
21 | debug{
22 | resValue "string", "TEST_BOT_SUFFIX", System.getenv("USER")
23 | }
24 | }
25 | }
26 |
27 | dependencies {
28 | compile fileTree(dir: 'libs', include: ['*.jar'])
29 | compile 'com.android.support:appcompat-v7:23.2.0'
30 | compile project(':respokeSDK')
31 | }
32 |
--------------------------------------------------------------------------------
/respokeSDKTest/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/jasonadams/android_sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/respokeSDKTest/respokeSDKTest.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/respokeSDKTest/runtests.sh:
--------------------------------------------------------------------------------
1 | # !/bin/bash
2 |
3 | #emulator @Nexus_One_API_19 &
4 |
5 | ./gradlew clean connectedAndroidTest --stacktrace
6 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/androidTest/java/com/digium/respokesdktest/RespokeActivityTestCase.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdktest;
12 |
13 | import android.content.Context;
14 | import android.test.ActivityInstrumentationTestCase2;
15 |
16 | import com.digium.respokesdk.Respoke;
17 | import com.digium.respokesdk.RespokeClient;
18 |
19 | import java.util.concurrent.CountDownLatch;
20 | import java.util.concurrent.TimeUnit;
21 |
22 |
23 | public abstract class RespokeActivityTestCase extends ActivityInstrumentationTestCase2 {
24 |
25 | public CountDownLatch asyncTaskSignal = new CountDownLatch(1);
26 |
27 |
28 | public RespokeActivityTestCase(Class activityClass) {
29 | super(activityClass);
30 | }
31 |
32 |
33 | public RespokeClient createTestClient(final String endpointID, RespokeClient.Listener listener, final Context context) {
34 | final RespokeClient client = Respoke.sharedInstance().createClient(context);
35 | assertNotNull("Should create test client", client);
36 | client.baseURL = RespokeTestCase.TEST_RESPOKE_BASE_URL;
37 |
38 | asyncTaskSignal = new CountDownLatch(1); // Reset the countdown signal
39 | client.setListener(listener);
40 | try {
41 | runTestOnUiThread(new Runnable() {
42 | @Override
43 | public void run() {
44 | client.connect(endpointID, RespokeTestCase.TEST_APP_ID, true, null, context, new RespokeClient.ConnectCompletionListener() {
45 | @Override
46 | public void onError(String errorMessage) {
47 | assertTrue("Should successfully connect. error: " + errorMessage, false);
48 | asyncTaskSignal.countDown();
49 | }
50 | });
51 | }
52 | });
53 |
54 | assertTrue("Client connect timed out", asyncTaskSignal.await(RespokeTestCase.TEST_TIMEOUT, TimeUnit.SECONDS));
55 | assertTrue("Test client should connect", client.isConnected());
56 |
57 | } catch (Throwable throwable) {
58 | assertTrue("Exception encountered while creating test client", false);
59 | }
60 |
61 | return client;
62 | }
63 |
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/androidTest/java/com/digium/respokesdktest/RespokeTestCase.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdktest;
12 |
13 | import android.content.Context;
14 | import android.os.Looper;
15 | import android.test.AndroidTestCase;
16 | import android.util.Log;
17 |
18 | import com.digium.respokesdk.Respoke;
19 | import com.digium.respokesdk.RespokeClient;
20 |
21 | /**
22 | * A test case base class to provide commonly used methods
23 | */
24 | public abstract class RespokeTestCase extends AndroidTestCase {
25 |
26 | public final static String TEST_RESPOKE_BASE_URL = "https://api.respoke.io";
27 | public final static String TEST_APP_ID = "REPLACE_ME_WITH_YOUR_APP_ID";
28 | public static final String TEST_MESSAGE = "This is a test message!";
29 | public static int TEST_TIMEOUT = 60; // Timeout in seconds
30 | public static int CALL_TEST_TIMEOUT = 60; // Timeout in seconds for calling tests (which take longer to setup)
31 | public boolean asyncTaskDone;
32 |
33 | private static final String TAG = "RespokeTestCase";
34 |
35 |
36 | public static String getTestBotEndpointId(Context context) {
37 | return "testbot-" + context.getResources().getText(R.string.TEST_BOT_SUFFIX);
38 | }
39 |
40 |
41 | public static String generateTestEndpointID() {
42 | String uuid = "";
43 | String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
44 | int rnd = 0;
45 | int r;
46 |
47 | for (int i = 0; i < 6; i += 1) {
48 | if (rnd <= 0x02) {
49 | rnd = (int) (0x2000000 + Math.round(java.lang.Math.random() * 0x1000000));
50 | }
51 | r = rnd & 0xf;
52 | rnd = rnd >> 4;
53 | uuid = uuid + chars.charAt(r);
54 | }
55 | return "test_user_" + uuid;
56 | }
57 |
58 |
59 | public boolean waitForCompletion(long timeoutSecs) {
60 | long timeoutDate = System.currentTimeMillis() + (timeoutSecs * 1000);
61 |
62 | try {
63 | do {
64 | if (System.currentTimeMillis() > timeoutDate) {
65 | Log.e(TAG, "network timeout");
66 | Log.e(TAG, "inside TapInspectTestCase : class = " + this.getClass().getSimpleName());
67 | break;
68 | }
69 | synchronized (this) {
70 | this.wait(100);
71 | }
72 | } while (!asyncTaskDone);
73 | } catch (InterruptedException e) {
74 | e.printStackTrace();
75 | return false;
76 | }
77 |
78 | return asyncTaskDone;
79 | }
80 |
81 |
82 | public RespokeClient createTestClient(String endpointID, RespokeClient.Listener listener) {
83 | final RespokeClient client = Respoke.sharedInstance().createClient(getContext());
84 | assertNotNull("Should create test client", client);
85 | client.baseURL = TEST_RESPOKE_BASE_URL;
86 |
87 | asyncTaskDone = false;
88 | client.setListener(listener);
89 | client.connect(endpointID, RespokeTestCase.TEST_APP_ID, true, null, getContext(), new RespokeClient.ConnectCompletionListener() {
90 | @Override
91 | public void onError(String errorMessage) {
92 | assertTrue("Should successfully connect", false);
93 | asyncTaskDone = true;
94 | }
95 | });
96 |
97 | assertTrue("Client connect timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
98 | assertTrue("Test client should connect", client.isConnected());
99 |
100 | return client;
101 | }
102 |
103 |
104 | public static boolean currentlyOnUIThread() {
105 | // Returns true if the current thread is the UI thread
106 | return Looper.myLooper() == Looper.getMainLooper();
107 | }
108 |
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/androidTest/java/com/digium/respokesdktest/functional/ConnectionTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdktest.functional;
12 |
13 | import android.util.Log;
14 |
15 | import com.digium.respokesdk.Respoke;
16 | import com.digium.respokesdk.RespokeCall;
17 | import com.digium.respokesdk.RespokeClient;
18 | import com.digium.respokesdk.RespokeDirectConnection;
19 | import com.digium.respokesdk.RespokeEndpoint;
20 | import com.digium.respokesdk.RespokeGroup;
21 | import com.digium.respokesdktest.RespokeTestCase;
22 |
23 | import java.util.Date;
24 |
25 |
26 | public class ConnectionTests extends RespokeTestCase implements RespokeClient.Listener {
27 |
28 | private boolean rateLimitHit;
29 | RespokeEndpoint endpoint;
30 |
31 | // Test temporarily disabled until socket library issues are resolved
32 | /*
33 | public void testRateLimiting() {
34 | // Create a client to test with
35 | final String testEndpointID = generateTestEndpointID();
36 | final RespokeClient client = createTestClient(testEndpointID, this);
37 |
38 | endpoint = client.getEndpoint(testEndpointID, false);
39 | assertNotNull("Should create endpoint instance", endpoint);
40 |
41 | asyncTaskDone = false;
42 |
43 | // Launch 5 simultaneous cascades of presence registration calls to the server, in an effort to hit the rate limit
44 | sendLoop(1);
45 | sendLoop(1);
46 | sendLoop(1);
47 | sendLoop(1);
48 | sendLoop(1);
49 |
50 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
51 | assertTrue("Should eventually hit a rate limit error", rateLimitHit);
52 | }
53 |
54 |
55 | public void sendLoop(final Integer attempt) {
56 | // Generally hits the rate limit around 20 attempts
57 | if (!rateLimitHit && (attempt < 30)) {
58 | endpoint.registerPresence(new Respoke.TaskCompletionListener() {
59 | @Override
60 | public void onSuccess() {
61 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
62 | sendLoop(attempt + 1);
63 | }
64 |
65 | @Override
66 | public void onError(String errorMessage) {
67 | if (!rateLimitHit) {
68 | assertTrue("Should indicate a rate limit error. received: " + errorMessage, errorMessage.equals("API rate limit was exceeded"));
69 | rateLimitHit = true;
70 | asyncTaskDone = true;
71 | }
72 | }
73 | });
74 | } else {
75 | asyncTaskDone = true;
76 | }
77 | }
78 | */
79 |
80 | // RespokeClient.Listener methods
81 |
82 |
83 | public void onConnect(RespokeClient sender) {
84 | asyncTaskDone = true;
85 | }
86 |
87 |
88 | public void onDisconnect(RespokeClient sender, boolean reconnecting) {
89 | assertTrue("Should not disconnect during testing", false);
90 | asyncTaskDone = true;
91 | }
92 |
93 |
94 | public void onError(RespokeClient sender, String errorMessage) {
95 | assertTrue("Should not produce any client errors during endpoint testing", false);
96 | asyncTaskDone = true;
97 | }
98 |
99 |
100 | public void onCall(RespokeClient sender, RespokeCall call) {
101 | // Not under test
102 | }
103 |
104 |
105 | public void onIncomingDirectConnection(RespokeDirectConnection directConnection, RespokeEndpoint endpoint) {
106 | // Not under test
107 | }
108 |
109 |
110 | public void onMessage(String message, RespokeEndpoint endpoint, RespokeGroup group, Date timestamp, Boolean didSend) {
111 | // Not under test
112 | }
113 |
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/androidTest/java/com/digium/respokesdktest/functional/DirectConnectionTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdktest.functional;
12 |
13 | import com.digium.respokesdk.Respoke;
14 | import com.digium.respokesdk.RespokeCall;
15 | import com.digium.respokesdk.RespokeClient;
16 | import com.digium.respokesdk.RespokeDirectConnection;
17 | import com.digium.respokesdk.RespokeEndpoint;
18 | import com.digium.respokesdk.RespokeGroup;
19 | import com.digium.respokesdktest.RespokeTestCase;
20 |
21 | import java.util.Date;
22 |
23 |
24 | public class DirectConnectionTests extends RespokeTestCase implements RespokeClient.Listener, RespokeEndpoint.Listener, RespokeCall.Listener, RespokeDirectConnection.Listener {
25 |
26 | private boolean callbackDidSucceed;
27 | private boolean messageReceived;
28 | private boolean didConnect;
29 | private boolean didHangup;
30 | private boolean didGetIncomingDirectConnection;
31 | private boolean didGetCallerOnOpen;
32 | private boolean didGetCalleeOnOpen;
33 | private boolean didGetCallerOnClose;
34 | private boolean didGetCalleeOnClose;
35 | private RespokeEndpoint firstEndpoint;
36 | private RespokeEndpoint secondEndpoint;
37 | private RespokeDirectConnection callerDirectConnection;
38 | private RespokeDirectConnection calleeDirectConnection;
39 | private Object receivedMessageObject;
40 |
41 |
42 | public void testDirectConnection() {
43 | // Create a client to test with
44 | final String testEndpointID = generateTestEndpointID();
45 | final RespokeClient firstClient = createTestClient(testEndpointID, this);
46 |
47 | // Create a second client to test with
48 | final String secondTestEndpointID = generateTestEndpointID();
49 | final RespokeClient secondClient = createTestClient(secondTestEndpointID, this);
50 |
51 | // Build references to each of the endpoints
52 | firstEndpoint = secondClient.getEndpoint(testEndpointID, false);
53 | assertNotNull("Should create endpoint instance", firstEndpoint);
54 | firstEndpoint.setListener(this);
55 |
56 | secondEndpoint = firstClient.getEndpoint(secondTestEndpointID, false);
57 | assertNotNull("Should create endpoint instance", secondEndpoint);
58 | secondEndpoint.setListener(this);
59 |
60 | // Start a direct connection between the two endpoints, calling from firstEndpoint
61 | asyncTaskDone = false;
62 | didGetIncomingDirectConnection = false;
63 | didGetCallerOnOpen = false;
64 | didGetCalleeOnOpen = false;
65 | callerDirectConnection = secondEndpoint.startDirectConnection();
66 | callerDirectConnection.setListener(this);
67 |
68 | RespokeCall call = callerDirectConnection.getCall();
69 | call.setListener(this);
70 |
71 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.CALL_TEST_TIMEOUT));
72 | assertTrue("Call should be established", didConnect);
73 | assertTrue("Callee client should have received an incoming direct connection notification", didGetIncomingDirectConnection);
74 | assertTrue("Caller should have received an onOpen notification", didGetCallerOnOpen);
75 | assertTrue("Callee should have received an onOpen notification", didGetCalleeOnOpen);
76 | assertTrue("Call should indicate that it is the caller", call.isCaller());
77 | assertTrue("Should indicate call is with the endpoint that the call was started from", secondEndpoint == call.endpoint);
78 | assertNotNull("Callee should have been notified about the incoming direct connection", calleeDirectConnection);
79 |
80 | // Test sending a text message over the direct connection
81 |
82 | asyncTaskDone = false;
83 | callbackDidSucceed = false;
84 | messageReceived = false;
85 | callerDirectConnection.sendMessage(TEST_MESSAGE, new Respoke.TaskCompletionListener() {
86 | @Override
87 | public void onSuccess() {
88 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
89 | callbackDidSucceed = true;
90 | asyncTaskDone = messageReceived;
91 | }
92 |
93 | @Override
94 | public void onError(String errorMessage) {
95 | assertTrue("Should not encounter an error when sending a message over a direct connection. Error: %@" + errorMessage, false);
96 | asyncTaskDone = true;
97 | }
98 | });
99 |
100 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
101 | assertTrue("sendMessage should have called the successHandler", callbackDidSucceed);
102 | assertTrue("Received message should be a string", receivedMessageObject instanceof String);
103 | assertTrue("Should have received correct message", receivedMessageObject.equals(TEST_MESSAGE));
104 |
105 | asyncTaskDone = false;
106 | didGetCallerOnClose = false;
107 | didGetCalleeOnClose = false;
108 | didHangup = false;
109 | call.hangup(true);
110 |
111 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
112 | assertTrue("Callee should have received onClose notification", didGetCalleeOnClose);
113 | assertTrue("Caller should have received onClose notification", didGetCallerOnClose);
114 | }
115 |
116 |
117 | // RespokeClient.Listener methods
118 |
119 |
120 | public void onConnect(RespokeClient sender) {
121 | asyncTaskDone = true;
122 | }
123 |
124 |
125 | public void onDisconnect(RespokeClient sender, boolean reconnecting) {
126 | // Not under test
127 | }
128 |
129 |
130 | public void onError(RespokeClient sender, String errorMessage) {
131 | assertTrue("Should not produce any client errors during testing", false);
132 | asyncTaskDone = true;
133 | }
134 |
135 |
136 | public void onCall(RespokeClient sender, RespokeCall call) {
137 | // Not under test
138 | }
139 |
140 |
141 | public void onIncomingDirectConnection(RespokeDirectConnection directConnection, RespokeEndpoint endpoint) {
142 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
143 | assertTrue("Should originate from the first Endpoint", endpoint == firstEndpoint);
144 | assertNotNull("DirectConnection object should not be nil", directConnection);
145 | calleeDirectConnection = directConnection;
146 | calleeDirectConnection.setListener(this);
147 | didGetIncomingDirectConnection = true;
148 |
149 | // Accept the call to continue the connection process
150 | directConnection.accept(getContext());
151 |
152 | asyncTaskDone = didConnect && didGetCallerOnOpen && didGetCalleeOnOpen;
153 | }
154 |
155 |
156 | public void onMessage(String message, RespokeEndpoint endpoint, RespokeGroup group, Date timestamp, Boolean didSend) {
157 | // Not under test
158 | }
159 |
160 |
161 | // RespokeEndpoint.Listener methods
162 |
163 |
164 | public void onMessage(String message, Date timestamp, RespokeEndpoint endpoint, boolean didSend) {
165 | assertTrue("No messages should have been received through the Respoke service", false);
166 | }
167 |
168 |
169 | public void onPresence(Object presence, RespokeEndpoint sender) {
170 | // Not under test
171 | }
172 |
173 |
174 | // RespokeCall.Listener methods
175 |
176 |
177 | public void onError(String errorMessage, RespokeCall sender) {
178 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
179 | assertTrue("Should perform a call without any errors. Error: " + errorMessage, false);
180 | asyncTaskDone = true;
181 | }
182 |
183 |
184 | public void onHangup(RespokeCall sender) {
185 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
186 | didHangup = true;
187 | asyncTaskDone = didGetCallerOnClose && didGetCalleeOnClose;
188 | }
189 |
190 |
191 | public void onConnected(RespokeCall sender) {
192 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
193 | didConnect = true;
194 | asyncTaskDone = didGetIncomingDirectConnection && didGetCallerOnOpen && didGetCalleeOnOpen;
195 | }
196 |
197 |
198 | public void directConnectionAvailable(RespokeDirectConnection directConnection, RespokeEndpoint endpoint) {
199 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
200 | assertTrue("Should reference correct direct connection object", directConnection == callerDirectConnection);
201 | assertTrue("Should reference correct remote endpoint", endpoint == secondEndpoint);
202 | }
203 |
204 |
205 | // RespokeDirectConnection.Listener methods
206 |
207 |
208 | public void onStart(RespokeDirectConnection sender) {
209 | // This callback will not be called in this test. It is only triggered when adding a directConnection to an existing call, which is currently not supported.
210 | }
211 |
212 |
213 | public void onOpen(RespokeDirectConnection sender) {
214 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
215 | if (sender == callerDirectConnection) {
216 | didGetCallerOnOpen = true;
217 | } else if (sender == calleeDirectConnection) {
218 | didGetCalleeOnOpen = true;
219 | } else {
220 | assertTrue("Should reference the correct direct connection object", false);
221 | }
222 |
223 | asyncTaskDone = didGetIncomingDirectConnection && didConnect && didGetCallerOnOpen && didGetCalleeOnOpen;
224 | }
225 |
226 |
227 | public void onClose(RespokeDirectConnection sender){
228 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
229 | if (sender == callerDirectConnection) {
230 | didGetCallerOnClose = true;
231 | } else if (sender == calleeDirectConnection) {
232 | didGetCalleeOnClose = true;
233 | } else {
234 | assertTrue("Should reference the correct direct connection object", false);
235 | }
236 |
237 | asyncTaskDone = didHangup && didGetCallerOnClose && didGetCalleeOnClose;
238 | }
239 |
240 |
241 | public void onMessage(String message, RespokeDirectConnection sender)
242 | {
243 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
244 | assertTrue("Should reference the correct direct connection object", sender == calleeDirectConnection);
245 | assertNotNull("message should not be null", message);
246 | receivedMessageObject = message;
247 | messageReceived = true;
248 | asyncTaskDone = callbackDidSucceed;
249 | }
250 |
251 | }
252 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/androidTest/java/com/digium/respokesdktest/functional/HangupTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdktest.functional;
12 |
13 | import com.digium.respokesdk.Respoke;
14 | import com.digium.respokesdk.RespokeCall;
15 | import com.digium.respokesdk.RespokeClient;
16 | import com.digium.respokesdk.RespokeDirectConnection;
17 | import com.digium.respokesdk.RespokeEndpoint;
18 | import com.digium.respokesdk.RespokeGroup;
19 | import com.digium.respokesdktest.MainActivity;
20 | import com.digium.respokesdktest.RespokeActivityTestCase;
21 | import com.digium.respokesdktest.RespokeTestCase;
22 |
23 | import java.util.Date;
24 | import java.util.concurrent.CountDownLatch;
25 | import java.util.concurrent.TimeUnit;
26 |
27 | public class HangupTests extends RespokeActivityTestCase implements RespokeClient.Listener, RespokeEndpoint.Listener, RespokeCall.Listener {
28 |
29 | private boolean callbackDidSucceed;
30 | private boolean firstCallDidHangup;
31 | private boolean secondCallDidHangup;
32 | private RespokeCall firstIncomingCall;
33 | private RespokeCall secondIncomingCall;
34 | private RespokeEndpoint testbotEndpoint;
35 | private RespokeClient firstClient;
36 | private RespokeClient secondClient;
37 |
38 |
39 | public HangupTests() {
40 | super(MainActivity.class);
41 | }
42 |
43 |
44 | public void testCallDeclineCCSelf() throws Throwable {
45 | // Create a client to test with
46 | final String testEndpointID = RespokeTestCase.generateTestEndpointID();
47 | firstClient = createTestClient(testEndpointID, this, getActivity());
48 | secondClient = createTestClient(testEndpointID, this, getActivity());
49 |
50 | // If things went well, there should be a web page open on the test host running a Transporter app that is logged in as testbot. It is set up to automatically initiate a call when asked via Respoke message
51 |
52 | testbotEndpoint = firstClient.getEndpoint(RespokeTestCase.getTestBotEndpointId(getActivity()), false);
53 | assertNotNull("Should create endpoint instance", testbotEndpoint);
54 | testbotEndpoint.setListener(this);
55 |
56 | // Send a quick message to make sure the test UI is running and produce a meaningful test error message
57 | asyncTaskSignal = new CountDownLatch(1); // Reset the countdown signal
58 | callbackDidSucceed = false;
59 | runTestOnUiThread(new Runnable() {
60 | @Override
61 | public void run() {
62 | testbotEndpoint.sendMessage(CallingTests.TEST_BOT_CALL_ME_MESSAGE, false, true, new Respoke.TaskCompletionListener() {
63 | @Override
64 | public void onSuccess() {
65 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
66 | callbackDidSucceed = true;
67 | if ((null != firstIncomingCall) && (null != secondIncomingCall)) {
68 | asyncTaskSignal.countDown();
69 | }
70 | }
71 |
72 | @Override
73 | public void onError(String errorMessage) {
74 | assertTrue("Should successfully send a message. Error: " + errorMessage, false);
75 | asyncTaskSignal.countDown();
76 | }
77 | });
78 | }
79 | });
80 |
81 | assertTrue("Test timed out", asyncTaskSignal.await(RespokeTestCase.TEST_TIMEOUT, TimeUnit.SECONDS));
82 | assertTrue("sendMessage should call onSuccess", callbackDidSucceed);
83 | assertNotNull("Should have created a call object to represent the incoming call", firstIncomingCall);
84 | assertTrue("Should be the recipient of the call, not the caller", !firstIncomingCall.isCaller());
85 | assertTrue("Should indicate call is with the endpoint that the call was started from", testbotEndpoint == firstIncomingCall.endpoint);
86 | assertTrue("Should indicate this is an audio-only call", firstIncomingCall.audioOnly);
87 | assertNotNull("Should have created a call object to represent the incoming call", secondIncomingCall);
88 | assertTrue("Should be the recipient of the call, not the caller", !secondIncomingCall.isCaller());
89 | assertTrue("Should indicate call is with the endpoint that the call was started from", testbotEndpoint.getEndpointID().equals(secondIncomingCall.endpoint.getEndpointID()));
90 | assertTrue("Should indicate this is an audio-only call", secondIncomingCall.audioOnly);
91 |
92 | // the incoming call has been detected by both clients. Decline the call on the first client, and wait for the hangup signal to be received by the 2nd client
93 | asyncTaskSignal = new CountDownLatch(1); // Reset the countdown signal
94 |
95 | runTestOnUiThread(new Runnable() {
96 | @Override
97 | public void run() {
98 | firstIncomingCall.hangup(true);
99 | }
100 | });
101 |
102 | assertTrue("Test timed out", asyncTaskSignal.await(RespokeTestCase.TEST_TIMEOUT, TimeUnit.SECONDS));
103 | assertTrue("First client should have hung up the call", firstCallDidHangup);
104 | assertTrue("Second client should have been notified of the hangup", secondCallDidHangup);
105 |
106 | firstClient.disconnect();
107 | Respoke.sharedInstance().unregisterClient(firstClient);
108 | secondClient.disconnect();
109 | Respoke.sharedInstance().unregisterClient(secondClient);
110 | }
111 |
112 |
113 | // RespokeClient.Listener methods
114 |
115 |
116 | public void onConnect(RespokeClient sender) {
117 | asyncTaskSignal.countDown();
118 | }
119 |
120 |
121 | public void onDisconnect(RespokeClient sender, boolean reconnecting) {
122 |
123 | }
124 |
125 |
126 | public void onError(RespokeClient sender, String errorMessage) {
127 | assertTrue("Should not produce any client errors during endpoint testing", false);
128 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
129 | asyncTaskSignal.countDown();
130 | }
131 |
132 |
133 | public void onCall(RespokeClient sender, RespokeCall call) {
134 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
135 |
136 | if (sender == firstClient) {
137 | firstIncomingCall = call;
138 | firstIncomingCall.setListener(this);
139 | } else if (sender == secondClient) {
140 | secondIncomingCall = call;
141 | secondIncomingCall.setListener(this);
142 | } else {
143 | assertTrue("Unrecognized client received an incoming call", false);
144 | }
145 |
146 | if ((callbackDidSucceed) && (null != firstIncomingCall) && (null != secondIncomingCall)) {
147 | asyncTaskSignal.countDown();
148 | }
149 | }
150 |
151 |
152 | public void onIncomingDirectConnection(RespokeDirectConnection directConnection, RespokeEndpoint endpoint) {
153 | // Not under test
154 | }
155 |
156 |
157 | public void onMessage(String message, RespokeEndpoint sender, RespokeGroup group, Date timestamp, Boolean didSend) {
158 | // Not under test
159 | }
160 |
161 |
162 | // RespokeEndpoint.Listener methods
163 |
164 |
165 | public void onMessage(String message, Date timestamp, RespokeEndpoint endpoint, boolean didSend) {
166 | // Not under test
167 | }
168 |
169 |
170 | public void onPresence(Object presence, RespokeEndpoint sender) {
171 | // Not under test
172 | }
173 |
174 |
175 | // RespokeCall.Listener methods
176 |
177 |
178 | public void onError(String errorMessage, RespokeCall sender) {
179 | assertTrue("Should perform a call without any errors. Error: " + errorMessage, false);
180 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
181 | asyncTaskSignal.countDown();
182 | }
183 |
184 |
185 | public void onHangup(RespokeCall sender) {
186 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
187 |
188 | if (sender == firstIncomingCall) {
189 | firstCallDidHangup = true;
190 | } else if (sender == secondIncomingCall) {
191 | secondCallDidHangup = true;
192 | } else {
193 | assertTrue("Unrecognized call received a hangup signal", false);
194 | }
195 |
196 | if (firstCallDidHangup && secondCallDidHangup) {
197 | asyncTaskSignal.countDown();
198 | }
199 | }
200 |
201 |
202 | public void onConnected(RespokeCall sender) {
203 | // Not under test
204 | }
205 |
206 |
207 | public void directConnectionAvailable(RespokeDirectConnection directConnection, RespokeEndpoint endpoint) {
208 | // Not under test
209 | }
210 |
211 | }
212 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/androidTest/java/com/digium/respokesdktest/functional/MessagingTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdktest.functional;
12 |
13 | import com.digium.respokesdk.Respoke;
14 | import com.digium.respokesdk.RespokeCall;
15 | import com.digium.respokesdk.RespokeClient;
16 | import com.digium.respokesdk.RespokeDirectConnection;
17 | import com.digium.respokesdk.RespokeEndpoint;
18 | import com.digium.respokesdk.RespokeGroup;
19 | import com.digium.respokesdktest.RespokeTestCase;
20 |
21 | import java.util.Date;
22 |
23 |
24 | public class MessagingTests extends RespokeTestCase implements RespokeClient.Listener, RespokeEndpoint.Listener {
25 |
26 | private boolean callbackDidSucceed;
27 | private boolean endpointMessageDelivered;
28 | private boolean endpointMessageCopied;
29 | private boolean doCopySelf;
30 | private boolean clientMessageDelivered;
31 | private boolean clientMessageCopied;
32 | private RespokeEndpoint recipientEndpoint;
33 | private RespokeEndpoint senderEndpoint;
34 |
35 |
36 | public void testEndpointMessaging() {
37 | doCopySelf = false;
38 | runMessagingTest();
39 | }
40 |
41 | public void testEndpointMessagingCCSelf() {
42 | doCopySelf = true;
43 | runMessagingTest();
44 | }
45 |
46 | /**
47 | * This test will create two client instances with unique endpoint IDs. It will then send messages between the two to test functionality.
48 | */
49 |
50 | public void runMessagingTest() {
51 | RespokeClient senderCCClient = null;
52 | RespokeEndpoint recipientCCEndpoint = null;
53 |
54 | // Create a client to test with
55 | final String recipientEndpointID = generateTestEndpointID();
56 | final RespokeClient recipientClient = createTestClient(recipientEndpointID, this);
57 |
58 | // Create a second client to test with
59 | final String senderEndpointID = generateTestEndpointID();
60 | final RespokeClient senderClient = createTestClient(senderEndpointID, this);
61 |
62 | // Build references to each of the endpoints
63 | recipientEndpoint = senderClient.getEndpoint(recipientEndpointID, false);
64 | assertNotNull("Should create endpoint instance", recipientEndpoint);
65 | recipientEndpoint.setListener(this);
66 |
67 | senderEndpoint = recipientClient.getEndpoint(senderEndpointID, false);
68 | assertNotNull("Should create endpoint instance", senderEndpoint);
69 | senderEndpoint.setListener(this);
70 |
71 | // Create new client and endpoint for copying self
72 | if (doCopySelf)
73 | {
74 | senderCCClient = createTestClient(senderEndpointID, this);
75 | assertNotNull("Should create sender CC client", senderCCClient);
76 |
77 | recipientCCEndpoint = senderCCClient.getEndpoint(recipientEndpointID, false);
78 | assertNotNull("Should create endpoint instance", recipientCCEndpoint);
79 | recipientCCEndpoint.setListener(this);
80 | }
81 |
82 | endpointMessageCopied = false;
83 | endpointMessageDelivered = false;
84 | clientMessageCopied = false;
85 | clientMessageDelivered = false;
86 | asyncTaskDone = false;
87 | callbackDidSucceed = false;
88 | recipientEndpoint.sendMessage(TEST_MESSAGE, false, doCopySelf, new Respoke.TaskCompletionListener() {
89 | @Override
90 | public void onSuccess() {
91 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
92 | callbackDidSucceed = true;
93 | tryCompleteTask();
94 | }
95 |
96 | @Override
97 | public void onError(String errorMessage) {
98 | assertTrue("Should successfully send a message", false);
99 | }
100 | });
101 |
102 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
103 | assertTrue("sendMessage should call successHandler", callbackDidSucceed);
104 | assertTrue("Should call RespokeEndpoint.onMessage listener when a message is received", endpointMessageDelivered);
105 | assertTrue("Should call RespokeClient.onMessage listener when a message is received", clientMessageDelivered);
106 |
107 | if (doCopySelf) {
108 | assertTrue("Should call RespokeEndpoint.onMessage listener with didSend=false when a message is copied", endpointMessageCopied);
109 | assertTrue("Should call RespokeClient.onMessage listener with didSend=false when a message is copied", clientMessageCopied);
110 | }
111 | }
112 |
113 | public void tryCompleteTask() {
114 | asyncTaskDone = callbackDidSucceed && endpointMessageDelivered && clientMessageDelivered;
115 | if (doCopySelf) {
116 | asyncTaskDone = asyncTaskDone && endpointMessageCopied && clientMessageCopied;
117 | }
118 | }
119 |
120 | // RespokeClient.Listener methods
121 |
122 |
123 | public void onConnect(RespokeClient sender) {
124 | asyncTaskDone = true;
125 | }
126 |
127 |
128 | public void onDisconnect(RespokeClient sender, boolean reconnecting) {
129 |
130 | }
131 |
132 |
133 | public void onError(RespokeClient sender, String errorMessage) {
134 | assertTrue("Should not produce any client errors during endpoint testing", false);
135 | asyncTaskDone = true;
136 | }
137 |
138 |
139 | public void onCall(RespokeClient sender, RespokeCall call) {
140 | // Not under test
141 | }
142 |
143 |
144 | public void onIncomingDirectConnection(RespokeDirectConnection directConnection, RespokeEndpoint endpoint) {
145 | // Not under test
146 | }
147 |
148 |
149 | public void onMessage(String message, RespokeEndpoint endpoint, RespokeGroup group, Date timestamp, Boolean didSend) {
150 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
151 | assertTrue("Message sent should be the message received", message.equals(TEST_MESSAGE));
152 | assertNotNull("Should include a timestamp", timestamp);
153 |
154 | if (!doCopySelf) {
155 | assertTrue("Endpoint should always be the sender", didSend);
156 | }
157 |
158 | assertNotNull("Endpoint passed to RespokeClient.onMessage listener should not be null", endpoint);
159 | final String onMessageEndpointID = endpoint.getEndpointID();
160 |
161 | if (didSend) {
162 | assertTrue("Should indicate correct sender endpointID", onMessageEndpointID.equals(senderEndpoint.getEndpointID()));
163 | clientMessageDelivered = true;
164 | } else {
165 | assertTrue("Should indicate correct sender endpointID", onMessageEndpointID.equals(recipientEndpoint.getEndpointID()));
166 | clientMessageCopied = true;
167 | }
168 |
169 | tryCompleteTask();
170 | }
171 |
172 |
173 | // RespokeEndpoint.Listener methods
174 |
175 |
176 | public void onMessage(String message, Date timestamp, RespokeEndpoint endpoint, boolean didSend) {
177 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
178 | assertTrue("Message sent should be the message received", message.equals(TEST_MESSAGE));
179 | assertNotNull("Should include a timestamp", timestamp);
180 |
181 | if (!doCopySelf) {
182 | assertTrue("Endpoint should always be the sender", didSend);
183 | }
184 |
185 | assertNotNull("Endpoint passed to RespokeEndpoint.onMessage listener should not be null", endpoint);
186 | final String onMessageEndpointID = endpoint.getEndpointID();
187 |
188 | if (didSend) {
189 | assertTrue("Should indicate correct sender endpointID", onMessageEndpointID.equals(senderEndpoint.getEndpointID()));
190 | endpointMessageDelivered = true;
191 | } else {
192 | assertTrue("Should indicate correct sender endpointID", onMessageEndpointID.equals(recipientEndpoint.getEndpointID()));
193 | endpointMessageCopied = true;
194 | }
195 |
196 | tryCompleteTask();
197 | }
198 |
199 |
200 | public void onPresence(Object presence, RespokeEndpoint sender) {
201 | // Not under test
202 | }
203 |
204 |
205 | }
206 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/androidTest/java/com/digium/respokesdktest/functional/PresenceTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdktest.functional;
12 |
13 | import com.digium.respokesdk.Respoke;
14 | import com.digium.respokesdk.RespokeCall;
15 | import com.digium.respokesdk.RespokeClient;
16 | import com.digium.respokesdk.RespokeDirectConnection;
17 | import com.digium.respokesdk.RespokeEndpoint;
18 | import com.digium.respokesdk.RespokeGroup;
19 | import com.digium.respokesdktest.RespokeTestCase;
20 |
21 | import java.util.ArrayList;
22 | import java.util.Date;
23 | import java.util.HashMap;
24 |
25 |
26 | public class PresenceTests extends RespokeTestCase implements RespokeClient.Listener, RespokeClient.ResolvePresenceListener, RespokeEndpoint.Listener {
27 |
28 | private boolean callbackDidSucceed;
29 | private boolean remotePresenceReceived;
30 | private Object customPresenceResolution;
31 | private Integer presenceCallBackCount;
32 | private boolean countingTest;
33 |
34 |
35 | public void testCustomResolveMethod() {
36 | // Create a client to test with
37 | final String testEndpointID = generateTestEndpointID();
38 | final RespokeClient firstClient = createTestClient(testEndpointID, this);
39 |
40 | // Create a second client to test with
41 | final String secondTestEndpointID = generateTestEndpointID();
42 | final RespokeClient secondClient = createTestClient(secondTestEndpointID, this);
43 |
44 | // The custom resolve function will always return this random value
45 | customPresenceResolution = Respoke.makeGUID();
46 | secondClient.setResolvePresenceListener(this);
47 |
48 | // Build references to each of the endpoints
49 | RespokeEndpoint firstEndpoint = secondClient.getEndpoint(testEndpointID, false);
50 | assertNotNull("Should create endpoint instance", firstEndpoint);
51 | firstEndpoint.setListener(this);
52 |
53 | RespokeEndpoint secondEndpoint = firstClient.getEndpoint(secondTestEndpointID, false);
54 | assertNotNull("Should create endpoint instance", secondEndpoint);
55 |
56 | asyncTaskDone = false;
57 | remotePresenceReceived = false;
58 | callbackDidSucceed = false;
59 | Object expectedRemotePresence = new HashMap();
60 | ((HashMap) expectedRemotePresence).put("presence", "nacho presence2");
61 | firstClient.setPresence(expectedRemotePresence, new Respoke.TaskCompletionListener() {
62 | @Override
63 | public void onSuccess() {
64 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
65 | callbackDidSucceed = true;
66 | asyncTaskDone = remotePresenceReceived;
67 | }
68 |
69 | @Override
70 | public void onError(String errorMessage) {
71 | assertTrue("Should successfully set presence updates. Error: " + errorMessage, false);
72 | }
73 | });
74 |
75 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
76 |
77 | assertTrue("Resolved presence should be a string", firstEndpoint.presence instanceof String);
78 | assertTrue("Resolved presence should be correct", customPresenceResolution.equals(firstEndpoint.presence));
79 | }
80 |
81 |
82 | public void testOfflineEndpointPresence() {
83 | // Create a client to test with
84 | final String testEndpointID = generateTestEndpointID();
85 | final RespokeClient client = createTestClient(testEndpointID, this);
86 |
87 | // Create a second random endpoint id to test with
88 | final String secondTestEndpointID = generateTestEndpointID();
89 |
90 | // Get an endpoint object to represent the second endpoint which is not online
91 | customPresenceResolution = "unavailable";
92 | RespokeEndpoint endpoint = client.getEndpoint(secondTestEndpointID, false);
93 | endpoint.setListener(this);
94 |
95 | assertNull("Presence should be null if the client has not registered for presence updates yet", endpoint.presence);
96 |
97 | asyncTaskDone = false;
98 | callbackDidSucceed = true; // Tell the onPresence listener method to signal asyncTaskDone = true right away
99 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
100 |
101 | assertNotNull(endpoint);
102 | assertNotNull("Presence should not be null", endpoint.presence);
103 | assertTrue("Presence should be unavailable", endpoint.presence.equals("unavailable"));
104 | }
105 |
106 |
107 | public void testPresenceRegistrationQueueing() {
108 | // Create a client to test with
109 | final String testEndpointID = generateTestEndpointID();
110 | final RespokeClient client = createTestClient(testEndpointID, this);
111 |
112 | // Create a second random endpoint id to test with
113 | final String secondTestEndpointID = generateTestEndpointID();
114 |
115 | countingTest = true;
116 | presenceCallBackCount = 0;
117 | RespokeEndpoint endpoint = client.getEndpoint(secondTestEndpointID, false);
118 | endpoint.setListener(this);
119 |
120 | // call getEndpoint several times again
121 | client.getEndpoint(secondTestEndpointID, false);
122 | client.getEndpoint(secondTestEndpointID, false);
123 | client.getEndpoint(secondTestEndpointID, false);
124 | client.getEndpoint(secondTestEndpointID, false);
125 | client.getEndpoint(secondTestEndpointID, false);
126 | client.getEndpoint(secondTestEndpointID, false);
127 | client.getEndpoint(secondTestEndpointID, false);
128 |
129 | // A single presence registration Runnable should have been queued, count the number of times the call back fires over the next few seconds
130 | asyncTaskDone = false;
131 | waitForCompletion(10);
132 |
133 | assertTrue("The presence callback should have occurred less than 3 times, but actually occurred " + presenceCallBackCount + " times.", presenceCallBackCount < 3);
134 | }
135 |
136 |
137 | // RespokeClient.Listener methods
138 |
139 |
140 | public void onConnect(RespokeClient sender) {
141 | asyncTaskDone = true;
142 | }
143 |
144 |
145 | public void onDisconnect(RespokeClient sender, boolean reconnecting) {
146 |
147 | }
148 |
149 |
150 | public void onError(RespokeClient sender, String errorMessage) {
151 | assertTrue("Should not produce any client errors during endpoint testing. Error: " + errorMessage, false);
152 | asyncTaskDone = true;
153 | }
154 |
155 |
156 | public void onCall(RespokeClient sender, RespokeCall call) {
157 | // Not under test
158 | }
159 |
160 |
161 | public void onIncomingDirectConnection(RespokeDirectConnection directConnection, RespokeEndpoint endpoint) {
162 | // Not under test
163 | }
164 |
165 |
166 | public void onMessage(String message, RespokeEndpoint endpoint, RespokeGroup group, Date timestamp, Boolean didSend) {
167 | // Not under test
168 | }
169 |
170 |
171 | // RespokeEndpoint.Listener methods
172 |
173 |
174 | public void onMessage(String message, Date timestamp, RespokeEndpoint endpoint, boolean didSend) {
175 | // Not under test
176 | }
177 |
178 |
179 | public void onPresence(Object presence, RespokeEndpoint sender) {
180 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
181 | assertNotNull("Remote presence should not be null", presence);
182 | assertTrue("Remote presence should be a string", presence instanceof String);
183 |
184 | if (countingTest) {
185 | presenceCallBackCount++;
186 | } else {
187 | assertTrue("Resolved presence should be correct. Expected " + customPresenceResolution + " but received " + presence, customPresenceResolution.equals((String) presence));
188 | remotePresenceReceived = true;
189 | asyncTaskDone = callbackDidSucceed;
190 | }
191 | }
192 |
193 |
194 | // RespokeClient.ResolvePresenceListener methods
195 |
196 |
197 | public Object resolvePresence(ArrayList presenceArray) {
198 | assertTrue("presence array should contain the correct number of values", 1 == presenceArray.size());
199 | return customPresenceResolution;
200 | }
201 |
202 |
203 | }
204 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/androidTest/java/com/digium/respokesdktest/unit/RespokeCallTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdktest.unit;
12 |
13 | import com.digium.respokesdk.RespokeCall;
14 | import com.digium.respokesdktest.RespokeTestCase;
15 |
16 | import org.json.JSONException;
17 | import org.json.JSONObject;
18 |
19 |
20 | public class RespokeCallTests extends RespokeTestCase {
21 |
22 |
23 | public void testSdpHasVideo() {
24 | try {
25 | JSONObject audioOnlySDP = new JSONObject("{\"type\":\"offer\",\"sdp\":\"v=0\\r\\no=- 6116182927273193777 2 IN IP4 127.0.0.1\\r\\ns=-\\r\\nt=0 0\\r\\na=group:BUNDLE audio\\r\\na=msid-semantic: WMS HxDm7ZdgkDE9aJpjeytJhT3stMgXxapdk8DY\\r\\nm=audio 9 RTP\\/SAVPF 111 103 104 9 0 8 106 105 13 126\\r\\nc=IN IP4 0.0.0.0\\r\\na=rtcp:9 IN IP4 0.0.0.0\\r\\na=ice-ufrag:AdddxlRYoMW8UICE\\r\\na=ice-pwd:BVFvVAKconjagjwsLlc8eoOq\\r\\na=fingerprint:sha-256 21:26:14:08:05:AD:D0:07:2F:8A:CC:D8:40:A9:A5:B9:72:5D:62:6D:83:1A:AF:76:35:F3:EA:4C:12:28:37:13\\r\\na=setup:actpass\\r\\na=mid:audio\\r\\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\\r\\na=extmap:3 http:\\/\\/www.webrtc.org\\/experiments\\/rtp-hdrext\\/abs-send-time\\r\\na=sendrecv\\r\\na=rtcp-mux\\r\\na=rtpmap:111 opus\\/48000\\/2\\r\\na=fmtp:111 minptime=10; useinbandfec=1\\r\\na=rtpmap:103 ISAC\\/16000\\r\\na=rtpmap:104 ISAC\\/32000\\r\\na=rtpmap:9 G722\\/8000\\r\\na=rtpmap:0 PCMU\\/8000\\r\\na=rtpmap:8 PCMA\\/8000\\r\\na=rtpmap:106 CN\\/32000\\r\\na=rtpmap:105 CN\\/16000\\r\\na=rtpmap:13 CN\\/8000\\r\\na=rtpmap:126 telephone-event\\/8000\\r\\na=maxptime:60\\r\\na=ssrc:3260245544 cname:EU2GZuWLO3m0+MJp\\r\\na=ssrc:3260245544 msid:HxDm7ZdgkDE9aJpjeytJhT3stMgXxapdk8DY 4c809bee-6b56-438a-ac90-d7053c0c4770\\r\\na=ssrc:3260245544 mslabel:HxDm7ZdgkDE9aJpjeytJhT3stMgXxapdk8DY\\r\\na=ssrc:3260245544 label:4c809bee-6b56-438a-ac90-d7053c0c4770\\r\\n\"}");
26 | JSONObject videoSDP = new JSONObject("{\"type\":\"offer\",\"sdp\":\"v=0\\r\\no=- 2269581771901782266 2 IN IP4 127.0.0.1\\r\\ns=-\\r\\nt=0 0\\r\\na=group:BUNDLE audio video\\r\\na=msid-semantic: WMS g7oxcNrUqwuwbUOvVLFMKO5OvWTOi8ee8ND6\\r\\nm=audio 9 RTP\\/SAVPF 111 103 104 9 0 8 106 105 13 126\\r\\nc=IN IP4 0.0.0.0\\r\\na=rtcp:9 IN IP4 0.0.0.0\\r\\na=ice-ufrag:0\\/4ED6Lq6RjLjTek\\r\\na=ice-pwd:BQdH\\/GJRrIOmFnrIEn0NRpzb\\r\\na=fingerprint:sha-256 21:26:14:08:05:AD:D0:07:2F:8A:CC:D8:40:A9:A5:B9:72:5D:62:6D:83:1A:AF:76:35:F3:EA:4C:12:28:37:13\\r\\na=setup:actpass\\r\\na=mid:audio\\r\\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\\r\\na=extmap:3 http:\\/\\/www.webrtc.org\\/experiments\\/rtp-hdrext\\/abs-send-time\\r\\na=sendrecv\\r\\na=rtcp-mux\\r\\na=rtpmap:111 opus\\/48000\\/2\\r\\na=fmtp:111 minptime=10; useinbandfec=1\\r\\na=rtpmap:103 ISAC\\/16000\\r\\na=rtpmap:104 ISAC\\/32000\\r\\na=rtpmap:9 G722\\/8000\\r\\na=rtpmap:0 PCMU\\/8000\\r\\na=rtpmap:8 PCMA\\/8000\\r\\na=rtpmap:106 CN\\/32000\\r\\na=rtpmap:105 CN\\/16000\\r\\na=rtpmap:13 CN\\/8000\\r\\na=rtpmap:126 telephone-event\\/8000\\r\\na=maxptime:60\\r\\na=ssrc:2445183102 cname:QOl3hq3Z4\\/FooJ+H\\r\\na=ssrc:2445183102 msid:g7oxcNrUqwuwbUOvVLFMKO5OvWTOi8ee8ND6 24650e1f-14f9-46ea-ba5a-6f5f86262d31\\r\\na=ssrc:2445183102 mslabel:g7oxcNrUqwuwbUOvVLFMKO5OvWTOi8ee8ND6\\r\\na=ssrc:2445183102 label:24650e1f-14f9-46ea-ba5a-6f5f86262d31\\r\\nm=video 9 RTP\\/SAVPF 100 116 117 96\\r\\nc=IN IP4 0.0.0.0\\r\\na=rtcp:9 IN IP4 0.0.0.0\\r\\na=ice-ufrag:0\\/4ED6Lq6RjLjTek\\r\\na=ice-pwd:BQdH\\/GJRrIOmFnrIEn0NRpzb\\r\\na=fingerprint:sha-256 21:26:14:08:05:AD:D0:07:2F:8A:CC:D8:40:A9:A5:B9:72:5D:62:6D:83:1A:AF:76:35:F3:EA:4C:12:28:37:13\\r\\na=setup:actpass\\r\\na=mid:video\\r\\na=extmap:2 urn:ietf:params:rtp-hdrext:toffset\\r\\na=extmap:3 http:\\/\\/www.webrtc.org\\/experiments\\/rtp-hdrext\\/abs-send-time\\r\\na=sendrecv\\r\\na=rtcp-mux\\r\\na=rtpmap:100 VP8\\/90000\\r\\na=rtcp-fb:100 ccm fir\\r\\na=rtcp-fb:100 nack\\r\\na=rtcp-fb:100 nack pli\\r\\na=rtcp-fb:100 goog-remb\\r\\na=rtpmap:116 red\\/90000\\r\\na=rtpmap:117 ulpfec\\/90000\\r\\na=rtpmap:96 rtx\\/90000\\r\\na=fmtp:96 apt=100\\r\\na=ssrc-group:FID 3991731061 1826556079\\r\\na=ssrc:3991731061 cname:QOl3hq3Z4\\/FooJ+H\\r\\na=ssrc:3991731061 msid:g7oxcNrUqwuwbUOvVLFMKO5OvWTOi8ee8ND6 b8e33db0-d73b-4695-ade9-9fea843a0111\\r\\na=ssrc:3991731061 mslabel:g7oxcNrUqwuwbUOvVLFMKO5OvWTOi8ee8ND6\\r\\na=ssrc:3991731061 label:b8e33db0-d73b-4695-ade9-9fea843a0111\\r\\na=ssrc:1826556079 cname:QOl3hq3Z4\\/FooJ+H\\r\\na=ssrc:1826556079 msid:g7oxcNrUqwuwbUOvVLFMKO5OvWTOi8ee8ND6 b8e33db0-d73b-4695-ade9-9fea843a0111\\r\\na=ssrc:1826556079 mslabel:g7oxcNrUqwuwbUOvVLFMKO5OvWTOi8ee8ND6\\r\\na=ssrc:1826556079 label:b8e33db0-d73b-4695-ade9-9fea843a0111\\r\\n\"}");
27 |
28 | assertTrue("Should indicate that SDP has video", RespokeCall.sdpHasVideo(videoSDP));
29 | assertTrue("Should indicate that SDP does not have video", !RespokeCall.sdpHasVideo(audioOnlySDP));
30 | } catch (JSONException e) {
31 | assertTrue("Could not build test SDPs", false);
32 | }
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/androidTest/java/com/digium/respokesdktest/unit/RespokeClientTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdktest.unit;
12 |
13 | import com.digium.respokesdk.Respoke;
14 | import com.digium.respokesdk.RespokeClient;
15 | import com.digium.respokesdk.RespokeGroup;
16 | import com.digium.respokesdktest.RespokeTestCase;
17 |
18 | import java.util.ArrayList;
19 |
20 |
21 | public class RespokeClientTests extends RespokeTestCase {
22 |
23 | private boolean callbackDidSucceed;
24 |
25 |
26 | public void testUnconnectedClientBehavior() {
27 | RespokeClient client = Respoke.sharedInstance().createClient(getContext());
28 | assertNotNull(client);
29 |
30 | assertFalse("Should indicate not connected", client.isConnected());
31 | assertNull("Local endpoint should be nil if never connected", client.getEndpointID());
32 |
33 | callbackDidSucceed = false;
34 | asyncTaskDone = false;
35 |
36 | ArrayList groupList = new ArrayList();
37 | groupList.add("newGroupID");
38 | client.joinGroups(groupList, new RespokeClient.JoinGroupCompletionListener() {
39 | @Override
40 | public void onSuccess(final ArrayList groupList) {
41 | assertTrue("Should not call success handler", false);
42 | asyncTaskDone = true;
43 | }
44 |
45 | @Override
46 | public void onError(String errorMessage) {
47 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
48 | callbackDidSucceed = true;
49 | assertEquals("Can't complete request when not connected. Please reconnect!", errorMessage);
50 | asyncTaskDone = true;
51 | }
52 | });
53 |
54 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
55 | assertTrue("Did not call error handler when not connected", callbackDidSucceed);
56 |
57 | // Should behave when calling disconnect on a client that is not connected (i.e. don't crash)
58 |
59 | client.disconnect();
60 |
61 | assertNull("Should return a nil presence when it has not been set", client.getPresence());
62 | }
63 |
64 |
65 | public void testConnectParameterErrorHandling() {
66 | RespokeClient client = Respoke.sharedInstance().createClient(getContext());
67 | assertNotNull(client);
68 |
69 | // Test bad parameters
70 |
71 | callbackDidSucceed = false;
72 | asyncTaskDone = false;
73 |
74 | client.connect("myEndpointID", null, false, null, getContext(), new RespokeClient.ConnectCompletionListener(){
75 | @Override
76 | public void onError(String errorMessage) {
77 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
78 | callbackDidSucceed = true;
79 | assertEquals("AppID and endpointID must be specified", errorMessage);
80 | asyncTaskDone = true;
81 | }
82 | });
83 |
84 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
85 | assertTrue("Did not call error handler", callbackDidSucceed);
86 |
87 | callbackDidSucceed = false;
88 | asyncTaskDone = false;
89 |
90 | client.connect(null, "anAwesomeAppID", false, null, getContext(), new RespokeClient.ConnectCompletionListener(){
91 | @Override
92 | public void onError(String errorMessage) {
93 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
94 | callbackDidSucceed = true;
95 | assertEquals("AppID and endpointID must be specified", errorMessage);
96 | asyncTaskDone = true;
97 | }
98 | });
99 |
100 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
101 | assertTrue("Did not call error handler", callbackDidSucceed);
102 |
103 | callbackDidSucceed = false;
104 | asyncTaskDone = false;
105 |
106 | client.connect("", "", false, null, getContext(), new RespokeClient.ConnectCompletionListener(){
107 | @Override
108 | public void onError(String errorMessage) {
109 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
110 | callbackDidSucceed = true;
111 | assertEquals("AppID and endpointID must be specified", errorMessage);
112 | asyncTaskDone = true;
113 | }
114 | });
115 |
116 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
117 | assertTrue("Did not call error handler", callbackDidSucceed);
118 |
119 |
120 | // Test more bad parameters
121 |
122 |
123 | callbackDidSucceed = false;
124 | asyncTaskDone = false;
125 |
126 | client.connect(null, null, getContext(), new RespokeClient.ConnectCompletionListener() {
127 | @Override
128 | public void onError(String errorMessage) {
129 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
130 | callbackDidSucceed = true;
131 | assertEquals("TokenID must be specified", errorMessage);
132 | asyncTaskDone = true;
133 | }
134 | });
135 |
136 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
137 | assertTrue("Did not call error handler", callbackDidSucceed);
138 |
139 | callbackDidSucceed = false;
140 | asyncTaskDone = false;
141 |
142 | client.connect("", null, getContext(), new RespokeClient.ConnectCompletionListener() {
143 | @Override
144 | public void onError(String errorMessage) {
145 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
146 | callbackDidSucceed = true;
147 | assertEquals("TokenID must be specified", errorMessage);
148 | asyncTaskDone = true;
149 | }
150 | });
151 |
152 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
153 | assertTrue("Did not call error handler", callbackDidSucceed);
154 |
155 |
156 | // Should fail silently if no error handler is specified (i.e. don't crash)
157 |
158 | client.connect("myEndpointID", null, false, null, getContext(), null);
159 | client.connect("", null, getContext(), null);
160 | }
161 |
162 |
163 | public void testGroupSearch() {
164 | RespokeClient client = Respoke.sharedInstance().createClient(getContext());
165 | assertNotNull(client);
166 |
167 | assertNull("Should return null for a null group", client.getGroup(null));
168 | assertNull("Should return null for a group that does not exist", client.getGroup("someGroup"));
169 | }
170 |
171 |
172 | }
173 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/androidTest/java/com/digium/respokesdktest/unit/RespokeEndpointTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdktest.unit;
12 |
13 | import android.app.Application;
14 | import android.test.ApplicationTestCase;
15 |
16 | import com.digium.respokesdk.Respoke;
17 | import com.digium.respokesdk.RespokeCall;
18 | import com.digium.respokesdk.RespokeClient;
19 | import com.digium.respokesdk.RespokeConnection;
20 | import com.digium.respokesdk.RespokeDirectConnection;
21 | import com.digium.respokesdk.RespokeEndpoint;
22 | import com.digium.respokesdktest.RespokeTestCase;
23 |
24 | import java.util.ArrayList;
25 | import java.util.Date;
26 | import java.util.Dictionary;
27 | import java.util.Enumeration;
28 | import java.util.HashMap;
29 | import java.util.concurrent.CountDownLatch;
30 | import java.util.concurrent.TimeUnit;
31 |
32 |
33 | public class RespokeEndpointTests extends RespokeTestCase implements RespokeEndpoint.Listener, RespokeClient.ResolvePresenceListener {
34 |
35 | private boolean callbackDidSucceed;
36 | private RespokeEndpoint presenceTestEndpoint;
37 | private Object callbackPresence;
38 | private Object customPresenceResolution;
39 | private static final String TEST_MESSAGE = "This is a test message!";
40 |
41 |
42 | public void testUnconnectedEndpointBehavior() {
43 | RespokeClient client = Respoke.sharedInstance().createClient(getContext());
44 | assertNotNull(client);
45 |
46 | assertNull("Should return nil if no endpoint exists", client.getEndpoint("someEndpointID", true));
47 |
48 | RespokeEndpoint endpoint = client.getEndpoint("someEndpointID", false);
49 | assertNotNull("Should create an endpoint instance if it does not exist and it so commanded to", endpoint);
50 | assertEquals("Should have the correct endpoint ID", "someEndpointID", endpoint.getEndpointID());
51 |
52 | ArrayList connections = endpoint.getConnections();
53 | assertNotNull("Should return an empty list of connections when not connected", connections);
54 | assertTrue("Should return an empty list of connections when not connected", 0 == connections.size());
55 |
56 | callbackDidSucceed = false;
57 | asyncTaskDone = false;
58 | endpoint.sendMessage("Hi there!", false, true, new Respoke.TaskCompletionListener(){
59 | @Override
60 | public void onSuccess() {
61 | assertTrue("Should not call success handler", false);
62 | asyncTaskDone = true;
63 | }
64 |
65 | @Override
66 | public void onError(String errorMessage) {
67 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
68 | callbackDidSucceed = true;
69 | assertEquals("Can't complete request when not connected. Please reconnect!", errorMessage);
70 | asyncTaskDone = true;
71 | }
72 | });
73 |
74 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
75 | assertTrue("Should call error handler", callbackDidSucceed);
76 | assertNull("Should not create a call object when not connected", endpoint.startCall(null, getContext(), null,false));
77 | }
78 |
79 |
80 | public void testPresence() {
81 | RespokeClient client = Respoke.sharedInstance().createClient(getContext());
82 | assertNotNull(client);
83 |
84 | assertNull("Should return nil if no endpoint exists", client.getEndpoint("someEndpointID", true));
85 |
86 | presenceTestEndpoint = client.getEndpoint("someEndpointID", false);
87 | assertNotNull("Should create an endpoint instance if it does not exist and it so commanded to", presenceTestEndpoint);
88 | presenceTestEndpoint.setListener(this);
89 |
90 | assertNull("Presence should initially be null", presenceTestEndpoint.presence);
91 |
92 |
93 | // Test presence with no connections
94 |
95 |
96 | callbackDidSucceed = false;
97 | callbackPresence = null;
98 | asyncTaskDone = false;
99 | presenceTestEndpoint.resolvePresence();
100 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
101 | assertTrue("Presence delegate should be called", callbackDidSucceed);
102 | assertTrue("Should resolve to the correct presence value", "unavailable".equals(callbackPresence));
103 |
104 |
105 | // Test presence with one connection
106 |
107 |
108 | RespokeConnection connection = new RespokeConnection(Respoke.makeGUID(), presenceTestEndpoint);
109 | assertNotNull("Should create connection", connection);
110 | assertNull("Presence should initially be null", connection.presence);
111 | presenceTestEndpoint.connections.add(connection);
112 |
113 | callbackDidSucceed = false;
114 | callbackPresence = null;
115 | asyncTaskDone = false;
116 | presenceTestEndpoint.resolvePresence();
117 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
118 | assertTrue("Presence delegate should be called", callbackDidSucceed);
119 | assertTrue("Should resolve to the correct presence value", "unavailable".equals(callbackPresence));
120 |
121 | ArrayList options = new ArrayList();
122 | options.add("chat");
123 | options.add("available");
124 | options.add("away");
125 | options.add("dnd");
126 | options.add("xa");
127 | options.add("unavailable");
128 |
129 | for (String eachPresence : options) {
130 | connection.presence = eachPresence;
131 |
132 | callbackDidSucceed = false;
133 | callbackPresence = null;
134 | asyncTaskDone = false;
135 | presenceTestEndpoint.resolvePresence();
136 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
137 | assertTrue("Presence listener should be called", callbackDidSucceed);
138 | assertTrue("Expected presence to be [" + eachPresence + "] but found [" + callbackPresence + "]", eachPresence.equals(connection.presence));
139 | assertTrue("Resolved endpoint presence should match the connections", eachPresence.equals(presenceTestEndpoint.presence));
140 | }
141 |
142 |
143 | // Test presence with 2 connections
144 |
145 |
146 | RespokeConnection secondConnection = new RespokeConnection(Respoke.makeGUID(), presenceTestEndpoint);
147 | assertNotNull("Should create connection", secondConnection);
148 | assertNull("Presence should initially be null", secondConnection.presence);
149 | presenceTestEndpoint.connections.add(secondConnection);
150 |
151 | for (Integer ii = 0; ii < options.size(); ii++) {
152 | String firstPresence = options.get(ii);
153 |
154 | for (Integer jj = 0; jj < options.size(); jj++) {
155 | String secondPresence = options.get(jj);
156 |
157 | connection.presence = firstPresence;
158 | secondConnection.presence = secondPresence;
159 |
160 | String expectedPresence = null;
161 |
162 | if (ii <= jj) {
163 | expectedPresence = firstPresence;
164 | } else {
165 | expectedPresence = secondPresence;
166 | }
167 |
168 | callbackDidSucceed = false;
169 | callbackPresence = null;
170 | asyncTaskDone = false;
171 | presenceTestEndpoint.resolvePresence();
172 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
173 | assertTrue("Presence delegate should be called", callbackDidSucceed);
174 | assertTrue("Expected presence to be [" + expectedPresence + "] but found [" + callbackPresence + "]", expectedPresence.equals(callbackPresence));
175 | assertTrue("Resolved endpoint presence should match the connections", expectedPresence.equals(presenceTestEndpoint.presence));
176 | }
177 | }
178 | }
179 |
180 |
181 | public void testCustomPresence() {
182 | RespokeClient client = Respoke.sharedInstance().createClient(getContext());
183 | assertNotNull(client);
184 | client.setResolvePresenceListener(this);
185 |
186 | presenceTestEndpoint = client.getEndpoint("someEndpointID", false);
187 | assertNotNull("Should create an endpoint instance if it does not exist and it so commanded to", presenceTestEndpoint);
188 | presenceTestEndpoint.setListener(this);
189 |
190 | RespokeConnection connection1 = new RespokeConnection(Respoke.makeGUID(), presenceTestEndpoint);
191 | assertNotNull("Should create connection", connection1);
192 | presenceTestEndpoint.connections.add(connection1);
193 |
194 | RespokeConnection connection2 = new RespokeConnection(Respoke.makeGUID(), presenceTestEndpoint);
195 | assertNotNull("Should create connection", connection2);
196 | presenceTestEndpoint.connections.add(connection2);
197 |
198 | RespokeConnection connection3 = new RespokeConnection(Respoke.makeGUID(), presenceTestEndpoint);
199 | assertNotNull("Should create connection", connection3);
200 | presenceTestEndpoint.connections.add(connection3);
201 |
202 |
203 | // Test presence values that are not strings
204 |
205 |
206 | customPresenceResolution = new HashMap();
207 | ((HashMap)customPresenceResolution).put("myRealPresence", "ready");
208 |
209 | HashMap connection1Presence = new HashMap();
210 | connection1Presence.put("myRealPresence", "not ready");
211 | connection1.presence = connection1Presence;
212 |
213 | connection2.presence = customPresenceResolution;
214 |
215 | HashMap connection3Presence = new HashMap();
216 | connection3Presence.put("myRealPresence", "not ready");
217 | connection3.presence = connection3Presence;
218 |
219 | callbackDidSucceed = false;
220 | callbackPresence = null;
221 | presenceTestEndpoint.resolvePresence();
222 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
223 | assertTrue("Presence delegate should be called", callbackDidSucceed);
224 | assertTrue("Custom presence should be a hash map", presenceTestEndpoint.presence instanceof HashMap);
225 | HashMap resolvedPresence = (HashMap)presenceTestEndpoint.presence;
226 | assertTrue("Should resolve to correct custom presence", resolvedPresence.get("myRealPresence").equals("ready"));
227 | assertTrue("Should resolve correct custom presence in callback", ((HashMap)callbackPresence).get("myRealPresence").equals("ready"));
228 | }
229 |
230 |
231 | // RespokeEndpoint.Listener methods
232 |
233 |
234 | public void onMessage(String message, Date timestamp, RespokeEndpoint endpoint, boolean didSend) {
235 | // Not under test
236 | }
237 |
238 |
239 | public void onPresence(Object presence, RespokeEndpoint sender) {
240 | assertTrue("Sender should be set correctly", sender == presenceTestEndpoint);
241 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
242 | callbackPresence = presence;
243 | callbackDidSucceed = true;
244 | asyncTaskDone = true;
245 | }
246 |
247 |
248 | // RespokeClient.ResolvePresenceListener methods
249 |
250 |
251 | public Object resolvePresence(ArrayList presenceArray) {
252 | assertTrue("presence array should contain the correct number of values", 3 == presenceArray.size());
253 | return customPresenceResolution;
254 | }
255 |
256 |
257 | }
258 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/androidTest/java/com/digium/respokesdktest/unit/RespokeGroupTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdktest.unit;
12 |
13 | import com.digium.respokesdk.Respoke;
14 | import com.digium.respokesdk.RespokeClient;
15 | import com.digium.respokesdk.RespokeConnection;
16 | import com.digium.respokesdk.RespokeGroup;
17 | import com.digium.respokesdktest.RespokeTestCase;
18 |
19 | import java.util.ArrayList;
20 |
21 |
22 | public class RespokeGroupTests extends RespokeTestCase {
23 |
24 | private boolean callbackDidSucceed;
25 |
26 |
27 | public void testUnconnectedGroupBehaviour() {
28 | RespokeClient client = Respoke.sharedInstance().createClient(getContext());
29 | assertNotNull(client);
30 |
31 | String testGroupID = "myGroupID";
32 | RespokeGroup group = new RespokeGroup(testGroupID, null, client);
33 |
34 | assertNotNull("Group should not be null", group);
35 | assertTrue("Should indicate group is not joined", !group.isJoined());
36 |
37 | callbackDidSucceed = false;
38 | asyncTaskDone = false;
39 | group.sendMessage("A message", false, new Respoke.TaskCompletionListener() {
40 | @Override
41 | public void onSuccess() {
42 | assertTrue("Should not send a message to a group that is not joined", false);
43 | asyncTaskDone = true;
44 | }
45 |
46 | @Override
47 | public void onError(String errorMessage) {
48 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
49 | assertTrue("Should return the correct error message", errorMessage.equals("Not a member of this group anymore."));
50 | callbackDidSucceed = true;
51 | asyncTaskDone = true;
52 | }
53 | });
54 |
55 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
56 | assertTrue("Should have called the error handler", callbackDidSucceed);
57 |
58 | callbackDidSucceed = false;
59 | asyncTaskDone = false;
60 |
61 | group.leave(new Respoke.TaskCompletionListener() {
62 | @Override
63 | public void onSuccess() {
64 | assertTrue("Leaving an unjoined group should fail", false);
65 | asyncTaskDone = true;
66 | }
67 |
68 | @Override
69 | public void onError(String errorMessage) {
70 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
71 | assertTrue("Should return the correct error message", "Not a member of this group anymore.".equals(errorMessage));
72 | callbackDidSucceed = true;
73 | asyncTaskDone = true;
74 | }
75 | });
76 |
77 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
78 | assertTrue("Should have called the error handler", callbackDidSucceed);
79 |
80 | callbackDidSucceed = false;
81 | asyncTaskDone = false;
82 | group.getMembers(new RespokeGroup.GetGroupMembersCompletionListener() {
83 | @Override
84 | public void onSuccess(ArrayList memberArray) {
85 | assertTrue("Getting members of an unjoined group should fail", false);
86 | asyncTaskDone = true;
87 | }
88 |
89 | @Override
90 | public void onError(String errorMessage) {
91 | assertTrue("Should be called in UI thread", RespokeTestCase.currentlyOnUIThread());
92 | assertTrue("Should return the correct error message", errorMessage.equals("Not a member of this group anymore."));
93 | callbackDidSucceed = true;
94 | asyncTaskDone = true;
95 | }
96 | });
97 |
98 | assertTrue("Test timed out", waitForCompletion(RespokeTestCase.TEST_TIMEOUT));
99 | assertTrue("Should have called the error handler", callbackDidSucceed);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/androidTest/java/com/digium/respokesdktest/unit/RespokeTests.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdktest.unit;
12 |
13 | import android.app.Application;
14 | import android.test.ApplicationTestCase;
15 |
16 | import com.digium.respokesdk.Respoke;
17 |
18 |
19 | public class RespokeTests extends ApplicationTestCase {
20 |
21 | public RespokeTests() {
22 | super(Application.class);
23 | }
24 |
25 |
26 | public void testMakeGUID() {
27 | String guid1 = Respoke.makeGUID();
28 | String guid2 = Respoke.makeGUID();
29 | String guid3 = Respoke.makeGUID();
30 | String guid4 = Respoke.makeGUID();
31 |
32 | assertTrue("GUIDs should be " + Respoke.GUID_STRING_LENGTH + " characters", Respoke.GUID_STRING_LENGTH == guid1.length());
33 | assertTrue("GUIDs should be " + Respoke.GUID_STRING_LENGTH + " characters", Respoke.GUID_STRING_LENGTH == guid2.length());
34 | assertTrue("GUIDs should be " + Respoke.GUID_STRING_LENGTH + " characters", Respoke.GUID_STRING_LENGTH == guid3.length());
35 | assertTrue("GUIDs should be " + Respoke.GUID_STRING_LENGTH + " characters", Respoke.GUID_STRING_LENGTH == guid4.length());
36 |
37 | assertFalse("Should create unique GUIDs every time", guid1.equals(guid2));
38 | assertFalse("Should create unique GUIDs every time", guid1.equals(guid3));
39 | assertFalse("Should create unique GUIDs every time", guid1.equals(guid4));
40 |
41 | assertFalse("Should create unique GUIDs every time", guid2.equals(guid3));
42 | assertFalse("Should create unique GUIDs every time", guid2.equals(guid4));
43 |
44 | assertFalse("Should create unique GUIDs every time", guid3.equals(guid4));
45 | }
46 |
47 |
48 | }
--------------------------------------------------------------------------------
/respokeSDKTest/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/main/java/com/digium/respokesdktest/MainActivity.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015, Digium, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under The MIT License found in the
6 | * LICENSE file in the root directory of this source tree.
7 | *
8 | * For all details and documentation: https://www.respoke.io
9 | */
10 |
11 | package com.digium.respokesdktest;
12 |
13 | import android.os.Bundle;
14 | import android.support.v7.app.AppCompatActivity;
15 | import android.view.Menu;
16 | import android.view.MenuItem;
17 |
18 |
19 | public class MainActivity extends AppCompatActivity {
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_main);
25 | }
26 |
27 |
28 | @Override
29 | public boolean onCreateOptionsMenu(Menu menu) {
30 | // Inflate the menu; this adds items to the action bar if it is present.
31 | getMenuInflater().inflate(R.menu.menu_main, menu);
32 | return true;
33 | }
34 |
35 | @Override
36 | public boolean onOptionsItemSelected(MenuItem item) {
37 | // Handle action bar item clicks here. The action bar will
38 | // automatically handle clicks on the Home/Up button, so long
39 | // as you specify a parent activity in AndroidManifest.xml.
40 | int id = item.getItemId();
41 |
42 | //noinspection SimplifiableIfStatement
43 | if (id == R.id.action_settings) {
44 | return true;
45 | }
46 |
47 | return super.onOptionsItemSelected(item);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/respoke/respoke-sdk-android/34a15f0558d29b1f1bc8481bbc5c505e855e05ef/respokeSDKTest/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/respokeSDKTest/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/respoke/respoke-sdk-android/34a15f0558d29b1f1bc8481bbc5c505e855e05ef/respokeSDKTest/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/respokeSDKTest/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/respoke/respoke-sdk-android/34a15f0558d29b1f1bc8481bbc5c505e855e05ef/respokeSDKTest/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/respokeSDKTest/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/respoke/respoke-sdk-android/34a15f0558d29b1f1bc8481bbc5c505e855e05ef/respokeSDKTest/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/respokeSDKTest/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SDK Test UI
3 |
4 | Hello world!
5 | Settings
6 |
7 |
--------------------------------------------------------------------------------
/respokeSDKTest/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':respokeSDKTest', ':respokeSDK'
2 | project(':respokeSDK').projectDir = new File('respokeSDK')
--------------------------------------------------------------------------------