├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build-smarthome.gradle ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lib ├── libactions_java_lib.jar └── libdialogflow_java_lib.jar ├── pom.xml ├── settings.gradle └── src ├── main ├── META-INF │ └── MANIFEST.MF ├── kotlin │ └── com │ │ └── google │ │ └── actions │ │ └── api │ │ ├── ActionContext.kt │ │ ├── ActionRequest.kt │ │ ├── ActionResponse.kt │ │ ├── ActionsSdkApp.kt │ │ ├── App.kt │ │ ├── Constants.kt │ │ ├── DefaultApp.kt │ │ ├── DialogflowApp.kt │ │ ├── ForIntent.kt │ │ ├── SessionEntityType.kt │ │ ├── impl │ │ ├── AogRequest.kt │ │ ├── AogResponse.kt │ │ ├── DialogflowRequest.kt │ │ ├── DialogflowResponse.kt │ │ └── io │ │ │ ├── RequestDeserializers.kt │ │ │ └── ResponseSerializer.kt │ │ ├── response │ │ ├── ResponseBuilder.kt │ │ └── helperintent │ │ │ ├── CompletePurchase.kt │ │ │ ├── Confirmation.kt │ │ │ ├── DateTimePrompt.kt │ │ │ ├── DeliveryAddress.kt │ │ │ ├── DigitalPurchaseCheck.kt │ │ │ ├── HelperIntent.kt │ │ │ ├── NewSurface.kt │ │ │ ├── Permission.kt │ │ │ ├── Place.kt │ │ │ ├── RegisterUpdate.kt │ │ │ ├── SelectionCarousel.kt │ │ │ ├── SelectionList.kt │ │ │ ├── SignIn.kt │ │ │ ├── TransactionDecision.kt │ │ │ ├── TransactionRequirements.kt │ │ │ ├── UpdatePermission.kt │ │ │ └── transactions │ │ │ └── v3 │ │ │ ├── TransactionDecision.kt │ │ │ └── TransactionRequirements.kt │ │ ├── smarthome │ │ ├── SmartHomeApp.kt │ │ ├── SmartHomeRequest.kt │ │ └── SmartHomeResponse.kt │ │ └── test │ │ └── MockRequestBuilder.kt ├── main.iml ├── proto │ └── google │ │ ├── api │ │ ├── annotations.proto │ │ └── http.proto │ │ └── home │ │ └── graph │ │ └── v1 │ │ ├── device.proto │ │ └── homegraph.proto └── resources │ └── metadata.properties └── test ├── kotlin └── com │ └── google │ └── actions │ └── api │ ├── AogRequestTest.kt │ ├── AogResponseTest.kt │ ├── DialogflowRequestTest.kt │ ├── DialogflowResponseTest.kt │ ├── smarthome │ ├── HomeGraphTest.kt │ ├── SmartHomeRequestTest.kt │ ├── SmartHomeResponseTest.kt │ └── TestSmartHomeApp.kt │ └── test │ └── MockRequestBuilderTest.kt ├── resources ├── aog_main.json ├── aog_place_error.json ├── aog_user_conversation_data.json ├── aog_user_storage.json ├── aog_with_all_surface_capabilities.json ├── aog_with_argument_extension.json ├── aog_with_arguments.json ├── aog_with_datetime.json ├── aog_with_delivery_address.json ├── aog_with_final_reprompt.json ├── aog_with_location.json ├── aog_with_option.json ├── aog_with_permission_denied.json ├── aog_with_place.json ├── aog_with_register_update.json ├── aog_with_reprompt.json ├── aog_with_transaction_decision.json ├── aog_with_transaction_requirements_check.json ├── aog_with_update_permission.json ├── dialogflow_complete.json ├── dialogflow_package_entitlements.json ├── dialogflow_user_storage.json ├── dialogflow_welcome.json ├── dialogflow_with_conv_data.json ├── dialogflow_with_datetime_response.json ├── input.json ├── smarthome_disconnect_request.json ├── smarthome_execute_2fa_request.json ├── smarthome_execute_2fa_response.json ├── smarthome_execute_customdata_request.json ├── smarthome_execute_dock_request.json ├── smarthome_execute_request.json ├── smarthome_execute_response.json ├── smarthome_query_customdata_request.json ├── smarthome_query_request.json ├── smarthome_query_response.json ├── smarthome_sync_request.json ├── smarthome_sync_response.json └── smarthome_sync_response_local.json └── test.iml /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | build/ 4 | out/ 5 | .DS_Store 6 | src/generated # Generated source files 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | ## Contributor License Agreements 4 | 5 | We'd love to accept your sample apps and patches! Before we can take them, we 6 | have to jump a couple of legal hurdles. 7 | 8 | Please fill out either the individual or corporate Contributor License Agreement 9 | (CLA). 10 | 11 | * If you are an individual writing original source code and you're sure you 12 | own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual). 13 | * If you work for a company that wants to allow you to contribute your work, 14 | then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate). 15 | 16 | Follow either of the two links above to access the appropriate CLA and 17 | instructions for how to sign and return it. Once we receive it, we'll be able to 18 | accept your pull requests. 19 | 20 | ## Contributing A Patch 21 | 22 | 1. Submit an issue describing your proposed change to the repo in question. 23 | 1. The repo owner will respond to your issue promptly. 24 | 1. If your proposed change is accepted, and you haven't already done so, sign a 25 | Contributor License Agreement (see details above). 26 | 1. Fork the desired repo, develop and test your code changes. 27 | 1. Ensure that your code adheres to the existing style in the sample to which 28 | you are contributing. Refer to the 29 | [Google Cloud Platform Samples Style Guide](https://github.com/GoogleCloudPlatform/Template/wiki/style.html) for the 30 | recommended coding standards for this organization. 31 | 1. Ensure that your code has an appropriate set of unit tests which all pass. 32 | 1. Submit a pull request. -------------------------------------------------------------------------------- /build-smarthome.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019, Google 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * This Gradle file contains build information specifically for smart home Actions. 19 | */ 20 | 21 | apply plugin: 'com.google.protobuf' 22 | protobuf { 23 | protoc { 24 | artifact = 'com.google.protobuf:protoc:3.2.0' 25 | } 26 | plugins { 27 | grpc { 28 | artifact = 'io.grpc:protoc-gen-grpc-java:1.2.0' 29 | } 30 | } 31 | generateProtoTasks { 32 | all().each { task -> 33 | task.plugins { 34 | grpc {} 35 | } 36 | } 37 | } 38 | generatedFilesBaseDir = "$projectDir/src/generated" 39 | } 40 | 41 | // Mark proto folders as source directories 42 | sourceSets.main.java.srcDirs += file("${projectDir}/src/generated/main/java") 43 | sourceSets.main.java.srcDirs += file("${projectDir}/src/generated/main/grpc") 44 | 45 | ext { 46 | GRPC_VERSION = "1.21.0" 47 | } 48 | 49 | dependencies { 50 | compile "io.grpc:grpc-netty-shaded:${GRPC_VERSION}" 51 | compile "io.grpc:grpc-protobuf:${GRPC_VERSION}" 52 | compile "io.grpc:grpc-stub:${GRPC_VERSION}" 53 | compile "io.grpc:grpc-auth:${GRPC_VERSION}" 54 | compile 'com.google.auth:google-auth-library-oauth2-http:0.11.0' 55 | compile 'com.google.protobuf:protobuf-java-util:3.6.1' 56 | compile 'javax.annotation:javax.annotation-api:1.3.2' 57 | 58 | testCompile "io.grpc:grpc-testing:${GRPC_VERSION}" 59 | testCompile "org.mockito:mockito-core:2.+" 60 | } 61 | 62 | // In this section you declare where to find the dependencies of your project 63 | repositories { 64 | mavenCentral() 65 | jcenter() 66 | } 67 | 68 | // Add a step to remove the generated protobufs during a clean 69 | clean.doLast { 70 | file("${projectDir}/src/generated/").deleteDir() 71 | println "Removed generated sources directory" 72 | } 73 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * This generated file contains a sample Java Library project to get you started. 5 | * For more details take a look at the Java Libraries chapter in the Gradle 6 | * user guide available at https://docs.gradle.org/4.7/userguide/java_library_plugin.html 7 | */ 8 | 9 | buildscript { 10 | ext.kotlin_version = '1.4.30' 11 | repositories { 12 | mavenCentral() 13 | } 14 | dependencies { 15 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 16 | classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.15" 17 | } 18 | } 19 | 20 | buildscript { 21 | repositories { 22 | jcenter() 23 | } 24 | 25 | dependencies { 26 | classpath "org.jetbrains.dokka:dokka-gradle-plugin:0.9.17" 27 | } 28 | } 29 | 30 | plugins { 31 | // Apply the java-library plugin to add support for Java Library 32 | id 'java-library' 33 | } 34 | 35 | apply plugin: 'org.jetbrains.dokka' 36 | 37 | dokka { 38 | outputFormat = 'javadoc' 39 | outputDirectory = "$buildDir/javadoc" 40 | } 41 | 42 | version = '1.0.2' 43 | 44 | dependencies { 45 | // Actions on Google JSON bindings 46 | compile files('lib/libactions_java_lib.jar') 47 | 48 | // Dialogflow JSON bindings 49 | compile files('lib/libdialogflow_java_lib.jar') 50 | 51 | // https://mvnrepository.com/artifact/com.google.code.gson/gson 52 | compile group: 'com.google.code.gson', name: 'gson', version: '2.8.4' 53 | 54 | // https://mvnrepository.com/artifact/com.google.http-client/google-http-client 55 | compile group: 'com.google.http-client', name: 'google-http-client', version: '1.23.0' 56 | 57 | // https://mvnrepository.com/artifact/org.json/json 58 | compile group: 'org.json', name: 'json', version: '20180130' 59 | 60 | // https://mvnrepository.com/artifact/org.slf4j/slf4j-api 61 | compile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.25' 62 | 63 | // https://mvnrepository.com/artifact/org.hamcrest/hamcrest-core 64 | testCompile group: 'org.hamcrest', name: 'hamcrest-core', version: '1.3' 65 | 66 | // https://mvnrepository.com/artifact/junit/junit 67 | testCompile group: 'junit', name: 'junit', version: '4.12' 68 | 69 | // https://mvnrepository.com/artifact/org.testng/testng 70 | testCompile group: 'org.testng', name: 'testng', version: '6.14.3' 71 | 72 | // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine 73 | testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.2.0' 74 | 75 | compileClasspath group: "com.github.rholder", name: "gradle-one-jar", version: "1.0.4" 76 | 77 | compile group: "org.jetbrains.kotlin", name: "kotlin-stdlib-jdk8", version: "$kotlin_version" 78 | } 79 | 80 | // In this section you declare where to find the dependencies of your project 81 | repositories { 82 | mavenCentral() 83 | } 84 | 85 | jar { 86 | manifest { 87 | attributes('Implementation-Title': project.name, 88 | 'Implementation-Version': project.version) 89 | } 90 | } 91 | 92 | test { 93 | useTestNG() 94 | } 95 | 96 | apply plugin: 'kotlin' 97 | compileKotlin { 98 | kotlinOptions { 99 | jvmTarget = "1.8" 100 | } 101 | } 102 | compileTestKotlin { 103 | kotlinOptions { 104 | jvmTarget = "1.8" 105 | } 106 | } 107 | 108 | apply from: 'build-smarthome.gradle' 109 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actions-on-google/actions-on-google-java/d99b1a20aca7dc0d8d259fa8ce21c1a1989d5745/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /lib/libactions_java_lib.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actions-on-google/actions-on-google-java/d99b1a20aca7dc0d8d259fa8ce21c1a1989d5745/lib/libactions_java_lib.jar -------------------------------------------------------------------------------- /lib/libdialogflow_java_lib.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actions-on-google/actions-on-google-java/d99b1a20aca7dc0d8d259fa8ce21c1a1989d5745/lib/libdialogflow_java_lib.jar -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user guide at https://docs.gradle.org/4.7/userguide/multi_project_builds.html 8 | */ 9 | rootProject.name = 'actions-on-google' 10 | -------------------------------------------------------------------------------- /src/main/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | 3 | -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/ActionContext.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api 18 | 19 | /** 20 | * ActionContext represent the current context of a user's request. 21 | * See [ActionContext](https://dialogflow.com/docs/contexts) 22 | */ 23 | class ActionContext(val name: String, val lifespan: Int? = 5) { 24 | var parameters: Map? = null 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/ActionRequest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api 18 | 19 | import com.google.api.services.actions_fulfillment.v2.model.* 20 | import com.google.api.services.dialogflow_fulfillment.v2.model.WebhookRequest 21 | import java.util.* 22 | 23 | /** 24 | * Defines requirements of an object that represents a request to the Actions 25 | * webhook. 26 | * 27 | * The JSON protocol for Actions on Google is described [here](https://developers.google.com/actions/build/json/). 28 | * If Dialogflow is used as an intermediary, the JSON protocol between 29 | * Dialogflow and the webhook is as described [here](https://developers.google.com/actions/build/json/dialogflow-webhook-json). 30 | * 31 | * The Java objects (POJO's) that represent the JSON payload are referred to as 32 | * "binding classes". AppRequest and WebhookRequest (if Dialogflow is used) are 33 | * binding classes that represent an Actions on Google request and Dialogflow 34 | * request respectively. ActionRequest wraps these binding classes and provides 35 | * additional helper methods for intuitive and easier access to frequently used 36 | * features. 37 | */ 38 | interface ActionRequest { 39 | /** 40 | * Binding class (POJO) that represents a Dialogflow request. This is set only 41 | * if the request is routed through Dialogflow. 42 | */ 43 | val webhookRequest: WebhookRequest? 44 | /** 45 | * The binding class (POJO) that represents an Actions on Google request. 46 | */ 47 | val appRequest: AppRequest? 48 | 49 | /** 50 | * @return The intent specified in this request. 51 | */ 52 | val intent: String 53 | 54 | /** 55 | * @return The user object. 56 | */ 57 | val user: User? 58 | 59 | /** 60 | * Returns the Locale of the user. 61 | */ 62 | val locale: Locale 63 | 64 | /** 65 | * @return Information about the raw input provided by the user. 66 | */ 67 | val rawInput: RawInput? 68 | 69 | /** @return Raw text of the user utterance. */ 70 | val rawText: String? 71 | 72 | /** 73 | * @return The Google Assistant client surface. This is different from Device 74 | * by the fact that multiple Assistant surfaces may live on the same device. 75 | */ 76 | val surface: Surface? 77 | 78 | /** 79 | * @return Information about the Device the user is using to interact with the 80 | * Google Assistant. 81 | */ 82 | val device: Device? 83 | 84 | /** 85 | * @return Surfaces available for cross-surface handoff. 86 | */ 87 | val availableSurfaces: List? 88 | 89 | /** 90 | * Key-value pair data that is persisted across conversations for a 91 | * particular user. 92 | */ 93 | val userStorage: MutableMap 94 | 95 | /** 96 | * Key-value pair data that is persisted across turns in the same 97 | * conversation session. 98 | */ 99 | val conversationData: MutableMap 100 | 101 | /** 102 | * @return Indicates whether request must be handled in the sandbox mode for 103 | * transactions. 104 | */ 105 | val isInSandbox: Boolean 106 | 107 | /** 108 | * @return An identifier that uniquely identifies the conversation. 109 | */ 110 | val sessionId: String? 111 | 112 | /** 113 | * Returns the number of subsequent reprompts related to silent input from the 114 | * user. This should be used along with the NO_INPUT intent to reprompt the 115 | * user for input in cases where the Google Assistant could not pick up any 116 | * speech or null if request has no information about reprompts. 117 | * 118 | * ``` Java 119 | * int repromptCount = request.getRepromptCount(); 120 | * if (repromptCount == 0) { 121 | * responseBuilder.add("What was that?").build(); 122 | * } else if (repromptCount == 1) { 123 | * responseBuilder.add( 124 | * "Sorry I didn't catch that. Could you please repeat?").build(); 125 | * } else if (request.isFinalReprompt()) { 126 | * responseBuilder 127 | * .add("Okay let's try this again later.") 128 | * .endConversation() 129 | * .build(); 130 | * } 131 | * ``` 132 | */ 133 | val repromptCount: Int? 134 | 135 | /** 136 | * Returns true if it is the final reprompt related to silent input from the 137 | * user, false otherwise. This should be used along with the NO_INPUT intent 138 | * to give the final response to the user after multiple silences and should 139 | * be a response which ends the conversation. 140 | * 141 | * Returns null if no information about final reprompt is available in the 142 | * request. 143 | * 144 | * ``` Java 145 | * int repromptCount = request.getRepromptCount(); 146 | * if (reprmptCount == 0) { 147 | * responseBuilder.add("What was that?").build(); 148 | * } else if (repromptCount == 1) { 149 | * responseBuilder.add( 150 | * "Sorry I didn't catch that. Could you please repeat?").build(); 151 | * } else if (request.isFinalReprompt()) { 152 | * responseBuilder 153 | * .add("Okay let's try this again later.") 154 | * .endConversation() 155 | * .build(); 156 | * } 157 | * ``` 158 | */ 159 | val isFinalPrompt: Boolean? 160 | 161 | /** 162 | * @param name Name of the argument. 163 | * @return The Argument specified by the name or null if not found. 164 | */ 165 | fun getArgument(name: String): Argument? 166 | 167 | /** 168 | * See: [Dialogflow Parameters](https://dialogflow.com/docs/actions-and-parameters) 169 | * @param name Name of the parameter. 170 | * @return Value of the parameter or null if parameter not found. 171 | */ 172 | fun getParameter(name: String): Any? 173 | 174 | /** 175 | * See [Dialogflow Context](https://dialogflow.com/docs/contexts) 176 | * @return The ActionContext for the specified name or null if no context 177 | * found for the name. 178 | */ 179 | fun getContext(name: String): ActionContext? 180 | 181 | /** 182 | * @return Collection of all [contexts](https://dialogflow.com/docs/contexts). 183 | */ 184 | fun getContexts(): List 185 | 186 | /** 187 | * @param capability 188 | * @return True if the device has the specified capability. 189 | */ 190 | fun hasCapability(capability: String): Boolean 191 | 192 | 193 | /** 194 | * Returns the status of a sign in request. 195 | * @return Whether user is signed in or false if the request has no 196 | * information about sign in status. 197 | */ 198 | fun isSignInGranted(): Boolean 199 | 200 | /** 201 | * Returns the status of a register updates request. 202 | * @return Whether updates have been registered or false if the request has no 203 | * information about update registration. 204 | */ 205 | fun isUpdateRegistered(): Boolean 206 | 207 | /** 208 | * @return User provided location or null if request has no information 209 | * about Place. 210 | */ 211 | fun getPlace(): Location? 212 | 213 | /** 214 | * @return Whether user has granted permission based on a previous permission 215 | * request or false if request has no information about permission. 216 | */ 217 | fun isPermissionGranted(): Boolean 218 | 219 | /** 220 | * @return Whether user has confirmed or not based on a previous confirmation 221 | * request or false if request has no information about confirmation. 222 | */ 223 | fun getUserConfirmation(): Boolean 224 | 225 | /** 226 | * @return DateTimePrompt provided by the user based on a previous permission 227 | * request or null if request has no information about date/time. 228 | */ 229 | fun getDateTime(): DateTime? 230 | 231 | /** 232 | * @return Status of a media event or null if request has no information 233 | * about media status. 234 | */ 235 | fun getMediaStatus(): String? 236 | 237 | /** 238 | * @return Text value of the option selected by the user in a List/Carousel. 239 | */ 240 | fun getSelectedOption(): String? 241 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/ActionResponse.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api 18 | 19 | import com.google.api.services.actions_fulfillment.v2.model.AppResponse 20 | import com.google.api.services.actions_fulfillment.v2.model.ExpectedIntent 21 | import com.google.api.services.actions_fulfillment.v2.model.RichResponse 22 | import com.google.api.services.dialogflow_fulfillment.v2.model.WebhookResponse 23 | 24 | /** 25 | * Defines requirements of an object that represents a response from the Actions 26 | * webhook. 27 | */ 28 | interface ActionResponse { 29 | 30 | /** 31 | * Whether a user response is expected. 32 | */ 33 | val expectUserResponse: Boolean? 34 | 35 | /** 36 | * Binding class (POJO) that represents a Dialogflow response. This is set 37 | * only if the request is routed through Dialogflow. 38 | */ 39 | val webhookResponse: WebhookResponse? 40 | 41 | /** 42 | * The binding class (POJO) that represents an Actions on Google response. 43 | */ 44 | val appResponse: AppResponse? 45 | 46 | /** 47 | * A rich response that can include audio, text, cards, suggestions and 48 | * structured data. 49 | */ 50 | val richResponse: RichResponse? 51 | 52 | /** 53 | * Helper intents tell the Assistant to momentarily take over the conversation 54 | * to obtain common data such as a user's full name, a date and time, or a 55 | * delivery address. When you request a helper, the Assistant presents a 56 | * standard, consistent UI to users to obtain this information, so you don't 57 | * have to design your own. 58 | */ 59 | val helperIntent: ExpectedIntent? 60 | 61 | /** 62 | * Returns the JSON representation of the response. 63 | */ 64 | fun toJson(): String 65 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/ActionsSdkApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api 18 | 19 | import com.google.actions.api.impl.AogRequest 20 | import com.google.actions.api.response.ResponseBuilder 21 | import org.slf4j.LoggerFactory 22 | 23 | /** 24 | * Implementation of App for ActionsSDK based webhook. Developers must extend 25 | * this class if they are using the ActionsSDK (as against Dialogflow) to handle 26 | * requests. 27 | * 28 | * Note that the value of the @ForIntent annotation must match (case-sensitive) 29 | * the name of the intent. 30 | * 31 | * Usage: 32 | * ``` Java 33 | * class MyActionsApp extends ActionsSdkApp { 34 | * 35 | * @ForIntent("welcome") 36 | * public CompletableFuture showWelcomeMessage( 37 | * ActionRequest request) { 38 | * // Intent handler implementation here. 39 | * } 40 | * ``` 41 | */ 42 | open class ActionsSdkApp : DefaultApp() { 43 | 44 | private companion object { 45 | val LOG = LoggerFactory.getLogger(ActionsSdkApp::class.java.name) 46 | } 47 | 48 | override fun createRequest(inputJson: String, headers: Map<*, *>?): ActionRequest { 49 | LOG.info("ActionsSdkApp.createRequest..") 50 | return AogRequest.create(inputJson, headers) 51 | } 52 | 53 | override fun getResponseBuilder(request: ActionRequest): ResponseBuilder { 54 | val responseBuilder = ResponseBuilder( 55 | usesDialogflow = false, 56 | sessionId = request.sessionId, 57 | conversationData = request.conversationData, 58 | userStorage = request.userStorage) 59 | return responseBuilder 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/App.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api 18 | 19 | import java.util.concurrent.CompletableFuture 20 | 21 | /** 22 | * Top level interface for the Actions webhook. Sub-classes must 23 | * implement the JSON based protocol for the Actions on Google Conversational 24 | * API as described [here](https://developers.google.com/actions/build/json/conversation-webhook-json). 25 | * 26 | * It is recommended that developers sub-class from DialogflowApp or 27 | * ActionsSdkApp to implement their intent handlers. 28 | */ 29 | interface App { 30 | /** 31 | * Processes the incoming JSON request and returns JSON as described in the 32 | * Actions on Google conversation [protocol](https://developers.google.com/actions/build/json/conversation-webhook-json). 33 | */ 34 | fun handleRequest( 35 | inputJson: String?, headers: Map<*, *>?): CompletableFuture 36 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/Constants.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api 18 | 19 | internal const val APP_DATA_CONTEXT = "_actions_on_google" 20 | internal const val APP_DATA_CONTEXT_LIFESPAN = 99 21 | 22 | /** Permission to request user's first and last name */ 23 | const val PERMISSION_NAME = "NAME" 24 | /** 25 | * Permission to request user's precise location, latitude/longitude, and 26 | * formatted address. 27 | */ 28 | const val PERMISSION_DEVICE_PRECISE_LOCATION = "DEVICE_PRECISE_LOCATION" 29 | /** 30 | * Permission to request user's coarse location, zip code, city, and 31 | * country code. 32 | */ 33 | const val PERMISSION_DEVICE_COARSE_LOCATION = "DEVICE_COARSE_LOCATION" 34 | /** Permission to send updates to the user */ 35 | const val PERMISSION_UPDATE = "UPDATE" 36 | /** Argument key for the ID of the user granting permission for updates */ 37 | const val ARG_UPDATES_USER_ID = "UPDATES_USER_ID" 38 | /** Name of argument to get the user selected option (eg: from a List). */ 39 | const val ARG_OPTION = "OPTION" 40 | /** Name of argument to get whether it is the final reprompt */ 41 | const val ARG_IS_FINAL_REPROMPT = "IS_FINAL_REPROMPT" 42 | /** Name of argument to get the reprompt count */ 43 | const val ARG_REPROMPT_COUNT = "REPROMPT_COUNT" 44 | /** Name of argument to get media status */ 45 | const val ARG_MEDIA_STATUS = "MEDIA_STATUS" 46 | /** Name of argument to get the datetime value selected by the user. */ 47 | const val ARG_DATETIME = "DATETIME" 48 | /** Name of the argument to get the confirmation response from user. */ 49 | const val ARG_CONFIRMATION = "CONFIRMATION" 50 | /** Name of the argument to get the permission response from the user. */ 51 | const val ARG_PERMISSION = "PERMISSION" 52 | /** Name of the argument to get the place / location response from the user. */ 53 | const val ARG_PLACE = "PLACE" 54 | /** Name of the argument to get register for update status. */ 55 | const val ARG_REGISTER_UPDATE = "REGISTER_UPDATE" 56 | /** Name of the argument to get the sign in status */ 57 | const val ARG_SIGN_IN = "SIGN_IN" 58 | 59 | enum class Capability(val value: String) { 60 | SCREEN_OUTPUT("actions.capability.SCREEN_OUTPUT"), 61 | AUDIO_OUTPUT("actions.capability.AUDIO_OUTPUT"), 62 | MEDIA_RESPONSE_AUDIO("actions.capability.MEDIA_RESPONSE_AUDIO"), 63 | WEB_BROWSER("actions.capability.WEB_BROWSER"), 64 | INTERACTIVE_CANVAS("actions.capability.INTERACTIVE_CANVAS") 65 | } 66 | 67 | enum class EntityOverrideMode { 68 | ENTITY_OVERRIDE_MODE_OVERRIDE, 69 | ENTITY_OVERRIDE_MODE_SUPPLEMENT 70 | } 71 | -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/DefaultApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api 18 | 19 | import com.google.actions.api.response.ResponseBuilder 20 | import org.slf4j.LoggerFactory 21 | import java.util.concurrent.CompletableFuture 22 | 23 | /** 24 | * Default implementation of an Actions App. This class provides most of the 25 | * functionality of an App such as request parsing and routing. 26 | */ 27 | abstract class DefaultApp : App { 28 | 29 | val errorMsg_badReturnValue = "The return value of an intent handler" + 30 | " must be ActionResponse or CompletableFuture" 31 | 32 | private companion object { 33 | val LOG = LoggerFactory.getLogger(DefaultApp::class.java.name) 34 | } 35 | 36 | /** 37 | * Creates an ActionRequest for the specified JSON and metadata. 38 | * @param inputJson The input JSON. 39 | * @param headers Map containing metadata, usually from the HTTP request 40 | * headers. 41 | */ 42 | abstract fun createRequest(inputJson: String, headers: Map<*, *>?): 43 | ActionRequest 44 | 45 | /** 46 | * @return A ResponseBuilder for this App. 47 | */ 48 | abstract fun getResponseBuilder(request: ActionRequest): ResponseBuilder 49 | 50 | override fun handleRequest( 51 | inputJson: String?, headers: Map<*, *>?): CompletableFuture { 52 | if (inputJson == null || inputJson.isEmpty()) { 53 | return handleError("Invalid or empty JSON") 54 | } 55 | 56 | val request: ActionRequest 57 | val future: CompletableFuture 58 | try { 59 | request = createRequest(inputJson, headers) 60 | future = routeRequest(request) 61 | } catch (e: Exception) { 62 | return handleError(e) 63 | } 64 | 65 | return future 66 | .thenApply { it.toJson() } 67 | .exceptionally { throwable -> throwable.message } 68 | } 69 | 70 | @Throws(Exception::class) 71 | fun routeRequest(request: ActionRequest): CompletableFuture { 72 | val intent = request.intent 73 | val forIntentType = ForIntent::class.java 74 | for (method in javaClass.declaredMethods) { 75 | if (method.isAnnotationPresent(forIntentType)) { 76 | val annotation = method.getAnnotation(forIntentType) 77 | val forIntent = annotation as ForIntent 78 | if (forIntent.value == intent) { 79 | val result = method.invoke(this, request) 80 | return if (result is ActionResponse) { 81 | CompletableFuture.completedFuture(result) 82 | } else if (result is CompletableFuture<*>) { 83 | result as CompletableFuture 84 | } else { 85 | LOG.warn(errorMsg_badReturnValue) 86 | throw Exception(errorMsg_badReturnValue) 87 | } 88 | } 89 | } 90 | } 91 | // Unable to find a method with the annotation matching the intent. 92 | LOG.warn("Intent handler not found: {}", intent) 93 | throw Exception("Intent handler not found - $intent") 94 | } 95 | 96 | fun handleError(exception: Exception): CompletableFuture { 97 | exception.printStackTrace() 98 | return handleError(exception.message) 99 | } 100 | 101 | private fun handleError(message: String?): CompletableFuture { 102 | val future = CompletableFuture() 103 | future.completeExceptionally(Exception(message)) 104 | return future 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/DialogflowApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api 18 | 19 | import com.google.actions.api.impl.DialogflowRequest 20 | import com.google.actions.api.response.ResponseBuilder 21 | 22 | /** 23 | * Implementation of App for Dialogflow based webhook. Developers must extend 24 | * this class if they are using Dialogflow (as against ActionsSDK) to handle 25 | * requests. The DialogflowApp parses the incoming JSON request into an 26 | * ActionRequest that encapsulates the JSON protocol between Dialogflow and 27 | * your webhook as described [here](https://developers.google.com/actions/build/json/dialogflow-webhook-json). 28 | * 29 | * Note that the value of the @ForIntent annotation must match (case-sensitive) 30 | * the name of the intent as defined in Dialogflow. 31 | * 32 | * Usage: 33 | * ``` Java 34 | * class MyActionsApp extends DialogflowApp { 35 | * 36 | * @ForIntent("welcome") 37 | * public CompletableFuture showWelcomeMessage( 38 | * ActionRequest request) { 39 | * ResponseBuilder builder = getResponseBuilder(); 40 | * builder.add("some text"); 41 | * // Intent handler implementation here. 42 | * } 43 | * ``` 44 | */ 45 | open class DialogflowApp : DefaultApp() { 46 | 47 | override fun createRequest(inputJson: String, headers: Map<*, *>?): ActionRequest { 48 | return DialogflowRequest.create(inputJson, headers) 49 | } 50 | 51 | override fun getResponseBuilder(request: ActionRequest): ResponseBuilder { 52 | val responseBuilder = ResponseBuilder( 53 | usesDialogflow = true, 54 | conversationData = request.conversationData, 55 | sessionId = request.sessionId, 56 | userStorage = request.userStorage) 57 | return responseBuilder 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/ForIntent.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api 18 | 19 | /** 20 | * Annotates a method in a sub-class of an App as an intent handler. 21 | */ 22 | @kotlin.annotation.Retention(AnnotationRetention.RUNTIME) 23 | annotation class ForIntent(val value: String = "") -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/SessionEntityType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api 18 | 19 | import com.google.api.services.dialogflow_fulfillment.v2.model.EntityTypeEntity 20 | 21 | /** 22 | * SessionEntityType represents a developer entity type that only exist during 23 | * the session it was created for. Can extend or replace developer entity types. 24 | * See [Session entities](https://cloud.google.com/dialogflow/docs/entities-session) 25 | */ 26 | class SessionEntityType(val name: String, val entityOverrideMode: EntityOverrideMode) { 27 | var entities: List? = ArrayList() 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/impl/AogResponse.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.impl 18 | 19 | import com.google.actions.api.ActionResponse 20 | import com.google.actions.api.impl.io.ResponseSerializer 21 | import com.google.actions.api.response.ResponseBuilder 22 | import com.google.api.services.actions_fulfillment.v2.model.* 23 | import com.google.api.services.dialogflow_fulfillment.v2.model.WebhookResponse 24 | import com.google.gson.Gson 25 | import java.util.* 26 | 27 | internal class AogResponse internal constructor( 28 | responseBuilder: ResponseBuilder) : ActionResponse { 29 | override var appResponse: AppResponse? = null 30 | override val webhookResponse: WebhookResponse? = null 31 | override var richResponse: RichResponse? = null 32 | override val expectUserResponse: Boolean 33 | 34 | internal var helperIntents: List? 35 | internal var conversationData: MutableMap? = null 36 | internal var userStorage: MutableMap? = null 37 | internal var sessionId: String? = null 38 | private var textIntent: ExpectedIntent? = null 39 | 40 | init { 41 | this.appResponse = responseBuilder.appResponse 42 | this.expectUserResponse = responseBuilder.expectUserResponse 43 | this.richResponse = responseBuilder.richResponse 44 | this.sessionId = responseBuilder.sessionId 45 | this.conversationData = responseBuilder.conversationData 46 | this.userStorage = responseBuilder.userStorage 47 | 48 | if (appResponse == null) { 49 | // If appResponse is provided, that supersedes all other values. 50 | if (richResponse == null) { 51 | if (responseBuilder.responseItems.size > 0 52 | || responseBuilder.suggestions.size > 0 53 | || responseBuilder.linkOutSuggestion != null) { 54 | richResponse = RichResponse() 55 | if (responseBuilder.responseItems.size > 0) { 56 | richResponse?.items = responseBuilder.responseItems 57 | } 58 | if (responseBuilder.suggestions.size > 0) { 59 | richResponse?.suggestions = responseBuilder.suggestions 60 | } 61 | if (responseBuilder.linkOutSuggestion != null) { 62 | richResponse?.linkOutSuggestion = responseBuilder.linkOutSuggestion 63 | } 64 | } 65 | } 66 | } 67 | this.helperIntents = responseBuilder.helperIntents 68 | this.textIntent = ExpectedIntent() 69 | this.textIntent 70 | ?.setIntent("actions.intent.TEXT") 71 | ?.setInputValueData(emptyMap()) 72 | } 73 | 74 | override val helperIntent: ExpectedIntent? 75 | get() = helperIntents?.get(0) 76 | 77 | internal fun prepareAppResponse() { 78 | if (appResponse == null) { 79 | appResponse = AppResponse() 80 | if (expectUserResponse) { 81 | ask() 82 | } else { 83 | close() 84 | } 85 | if (conversationData != null) { 86 | val dataMap = HashMap() 87 | dataMap["data"] = conversationData 88 | appResponse?.conversationToken = Gson().toJson(dataMap) 89 | } 90 | if (userStorage != null) { 91 | val dataMap = HashMap() 92 | dataMap["data"] = userStorage 93 | appResponse?.userStorage = Gson().toJson(dataMap) 94 | } 95 | } 96 | } 97 | 98 | @Throws(IllegalStateException::class) 99 | private fun close() { 100 | appResponse?.expectUserResponse = expectUserResponse 101 | val finalResponse = FinalResponse() 102 | if (richResponse != null) { 103 | finalResponse.richResponse = richResponse 104 | } else { 105 | if (richResponse?.items != null || richResponse?.suggestions != null) { 106 | finalResponse.richResponse = richResponse 107 | } 108 | } 109 | appResponse?.finalResponse = finalResponse 110 | } 111 | 112 | @Throws(IllegalStateException::class) 113 | private fun ask() { 114 | appResponse?.expectUserResponse = true 115 | val inputPrompt = InputPrompt() 116 | if (richResponse != null) { 117 | inputPrompt.richInitialPrompt = richResponse 118 | } 119 | val expectedInput = ExpectedInput() 120 | if (inputPrompt.richInitialPrompt != null) { 121 | expectedInput.inputPrompt = inputPrompt 122 | } 123 | 124 | if (helperIntents != null) { 125 | expectedInput.possibleIntents = helperIntents 126 | } else { 127 | expectedInput.possibleIntents = listOf(textIntent) 128 | } 129 | 130 | val expectedInputs = ArrayList() 131 | expectedInputs.add(expectedInput) 132 | appResponse?.expectedInputs = expectedInputs 133 | } 134 | 135 | override fun toJson(): String { 136 | return ResponseSerializer(sessionId).toJsonV2(this) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/impl/DialogflowResponse.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.impl 18 | 19 | import com.google.actions.api.ActionContext 20 | import com.google.actions.api.ActionResponse 21 | import com.google.actions.api.SessionEntityType 22 | import com.google.actions.api.impl.io.ResponseSerializer 23 | import com.google.actions.api.response.ResponseBuilder 24 | import com.google.api.services.actions_fulfillment.v2.model.AppResponse 25 | import com.google.api.services.actions_fulfillment.v2.model.ExpectedIntent 26 | import com.google.api.services.actions_fulfillment.v2.model.RichResponse 27 | import com.google.api.services.dialogflow_fulfillment.v2.model.WebhookResponse 28 | 29 | internal class DialogflowResponse internal constructor( 30 | responseBuilder: ResponseBuilder) : ActionResponse { 31 | override val webhookResponse: WebhookResponse 32 | override val appResponse: AppResponse? = null 33 | override val expectUserResponse: Boolean? 34 | get() = googlePayload?.expectUserResponse 35 | 36 | internal var conversationData: MutableMap? = null 37 | internal var googlePayload: AogResponse? = null 38 | internal var contexts: MutableList? = ArrayList() 39 | internal var sessionEntityTypes: MutableList? = ArrayList() 40 | internal var sessionId: String? = null 41 | 42 | init { 43 | conversationData = responseBuilder.conversationData 44 | sessionId = responseBuilder.sessionId 45 | if (responseBuilder.webhookResponse != null) { 46 | webhookResponse = responseBuilder.webhookResponse!! 47 | } else { 48 | webhookResponse = WebhookResponse() 49 | } 50 | if (webhookResponse.fulfillmentText == null) { 51 | webhookResponse.fulfillmentText = responseBuilder.fulfillmentText 52 | } 53 | googlePayload = responseBuilder.buildAogResponse() 54 | } 55 | 56 | override val richResponse: RichResponse? 57 | get() = googlePayload?.richResponse 58 | 59 | override val helperIntent: ExpectedIntent? 60 | get() = googlePayload?.helperIntent 61 | 62 | override fun toJson(): String { 63 | return ResponseSerializer(sessionId).toJsonV2(this) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/CompletePurchase.kt: -------------------------------------------------------------------------------- 1 | package com.google.actions.api.response.helperintent 2 | 3 | import com.google.api.services.actions_fulfillment.v2.model.CompletePurchaseValueSpec 4 | import com.google.api.services.actions_fulfillment.v2.model.SkuId 5 | 6 | /** 7 | * Helper intent to build a digital transaction (virtual good) request. 8 | * 9 | * ``` Java 10 | * @ForIntent("buildOrder") 11 | * public CompletableFuture askForConfirmation(ActionRequest request) { 12 | * ResponseBuilder responseBuilder = getResponseBuilder(); 13 | * responseBuilder 14 | * .add(new CompletePurchase() 15 | * .setSkuId(skuId) 16 | * .setDeveloperPayload("Optional developer payload string)); 17 | * return CompletableFuture.completedFuture(responseBuilder.build()); 18 | * } 19 | * ``` 20 | * 21 | * The following code demonstrates how to handle the purchase response from the Google Play store. 22 | * 23 | * ``` Java 24 | * @ForIntent("actions_intent_COMPLETE_PURCHASE") 25 | * public CompletableFuture handlePurchaseResponse( 26 | * ActionRequest request) { 27 | * ResponseBuilder responseBuilder = getResponseBuilder(); 28 | * String purchaseResult = request.getArgument("COMPLETE_PURCHASE_VALUE").getTextValue(); 29 | * if (purchaseResult.equalsIgnoreCase("PURCHASE_STATUS_OK")) { 30 | * responseBuilder.add("Purchase completed! You're all set!").endConversation(); 31 | * } else if (purchaseResult.equalsIgnoreCase("PURCHASE_STATUS_ALREADY_OWNED")) { 32 | * responseBuilder.add("Purchase failed. You already own the item.").endConversation(); 33 | * } else if (purchaseResult.equalsIgnoreCase("PURCHASE_STATUS_ITEM_UNAVAILABLE")) { 34 | * responseBuilder.add("Purchase failed. Item is not available.").endConversation(); 35 | * } else if (purchaseResult.equalsIgnoreCase("PURCHASE_STATUS_ITEM_CHANGE_REQUESTED")) { 36 | * responseBuilder.add("Purchase failed. Item is not available.").endConversation(); 37 | * } else if (purchaseResult.equalsIgnoreCase("PURCHASE_STATUS_USER_CANCELLED")) { 38 | * responseBuilder.add("Purchase failed. Item is not available.").endConversation(); 39 | * } else if (purchaseResult.equalsIgnoreCase("PURCHASE_STATUS_ERROR") || 40 | * purchaseResult.equalsIgnoreCase("PURCHASE_STATUS_UNSPECIFIED")) { 41 | * responseBuilder.add("Purchase failed. Do you want to try again?").endConversation(); 42 | * } else { 43 | * responseBuilder.add("There was an internal error. Please try again later").endConversation(); 44 | * } 45 | * return CompletableFuture.completedFuture(responseBuilder.build()); 46 | * } 47 | * ``` 48 | */ 49 | class CompletePurchase : HelperIntent { 50 | private val map: HashMap = HashMap() 51 | private var skuId: SkuId? = null 52 | private var developerPayload: String? = null 53 | 54 | fun setSkuId(skuId: SkuId): CompletePurchase { 55 | this.skuId = skuId 56 | return this 57 | } 58 | 59 | fun setDeveloperPayload(developerPayload: String): CompletePurchase { 60 | this.developerPayload = developerPayload 61 | return this 62 | } 63 | 64 | override val name: String 65 | get() = "actions.intent.COMPLETE_PURCHASE" 66 | 67 | override val parameters: Map 68 | get() { 69 | prepareMap() 70 | map.put("@type", 71 | "type.googleapis.com/google.actions.transactions.v3.CompletePurchaseValueSpec") 72 | return map 73 | } 74 | 75 | private fun prepareMap() { 76 | val spec = CompletePurchaseValueSpec() 77 | spec.skuId = skuId 78 | spec.developerPayload = developerPayload 79 | 80 | spec.toMap(map) 81 | } 82 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/Confirmation.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | import com.google.api.services.actions_fulfillment.v2.model.ConfirmationValueSpecConfirmationDialogSpec 20 | 21 | /** 22 | * Helper intent response to ask user for confirmation. 23 | * 24 | * ``` Java 25 | * @ForIntent("askForConfirmation") 26 | * public CompletableFuture askForConfirmation(ActionRequest request) { 27 | * ResponseBuilder responseBuilder = getResponseBuilder(); 28 | * responseBuilder 29 | * .add("Placeholder for confirmation text") 30 | * .add(new Confirmation().setConfirmationText("Are you sure?")); 31 | * return CompletableFuture.completedFuture(responseBuilder.build()); 32 | * } 33 | * ``` 34 | * 35 | * The following code demonstrates how to handle the confirmation response from 36 | * the user. 37 | * 38 | * ``` Java 39 | * @ForIntent("actions_intent_confirmation") 40 | * public CompletableFuture handleConfirmationResponse( 41 | * ActionRequest request) { 42 | * boolean userResponse = request.getUserConfirmation() != null && 43 | * request.getUserConfirmation().booleanValue(); 44 | * ResponseBuilder responseBuilder = getResponseBuilder(); 45 | * responseBuilder.add( 46 | * userResponse ? "Thank you for confirming" : 47 | * "No problem. We won't bother you"); 48 | * return CompletableFuture.completedFuture(responseBuilder.build()); 49 | * } 50 | * ``` 51 | */ 52 | class Confirmation : HelperIntent { 53 | private var confirmationText: String? = null 54 | 55 | private val map = HashMap() 56 | 57 | fun setConfirmationText(confirmationText: String): Confirmation { 58 | this.confirmationText = confirmationText 59 | return this 60 | } 61 | 62 | override val name: String 63 | get() = "actions.intent.CONFIRMATION" 64 | 65 | override val parameters: Map 66 | get() { 67 | prepareMap() 68 | return map 69 | } 70 | 71 | private fun prepareMap() { 72 | map.put("@type", "type.googleapis.com/google.actions.v2.ConfirmationValueSpec") 73 | map.put("dialogSpec", ConfirmationValueSpecConfirmationDialogSpec() 74 | .setRequestConfirmationText(confirmationText)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/DateTimePrompt.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | import com.google.api.services.actions_fulfillment.v2.model.DateTimeValueSpecDateTimeDialogSpec 20 | 21 | /** 22 | * Helper intent response to ask user for a timezone agnostic date and time. 23 | * 24 | * ``` Java 25 | * @ForIntent("askForDateTime") 26 | * public CompletableFuture askForDateTime(ActionRequest request) { 27 | * ResponseBuilder responseBuilder = getResponseBuilder(); 28 | * responseBuilder 29 | * .add(new DateTimePrompt() 30 | * .setDateTimePrompt("When do you want to come in?") 31 | * .setDatePrompt("Which date works for you?") 32 | * .setTimePrompt("What time works for you?")); 33 | * return CompletableFuture.completedFuture(responseBuilder.build()); 34 | * } 35 | * ``` 36 | * 37 | * The following code demonstrates how to get the user's response: 38 | * 39 | * ``` Java 40 | * @ForIntent("actions_intent_datetime") 41 | * public CompletableFuture handleDateTimeResponse(ActionRequest request) { 42 | * ResponseBuilder responseBuilder = getResponseBuilder(); 43 | * DateTime dateTimeValue = request.getDateTime(); 44 | * String response; 45 | * if (dateTimeValue != null) { 46 | * response = "Thank you for your response. We will see you on " + 47 | * dateTimeValue.getDate(); 48 | * } else { 49 | * response = "Sorry, I didn't get that."; 50 | * } 51 | * responseBuilder.add(response); 52 | * return CompletableFuture.completedFuture(responseBuilder.build()); 53 | * } 54 | */ 55 | class DateTimePrompt : HelperIntent { 56 | 57 | private var dateTimePrompt: String? = null 58 | private var datePrompt: String? = null 59 | private var timePrompt: String? = null 60 | 61 | private val map = HashMap() 62 | 63 | fun setDateTimePrompt(prompt: String): DateTimePrompt { 64 | this.dateTimePrompt = prompt 65 | return this 66 | } 67 | 68 | fun setDatePrompt(prompt: String): DateTimePrompt { 69 | this.datePrompt = prompt 70 | return this 71 | } 72 | 73 | fun setTimePrompt(prompt: String): DateTimePrompt { 74 | this.timePrompt = prompt 75 | return this 76 | } 77 | 78 | private fun prepareMap() { 79 | map.put("@type", "type.googleapis.com/google.actions.v2.DateTimeValueSpec") 80 | map.put("dialogSpec", 81 | DateTimeValueSpecDateTimeDialogSpec() 82 | .setRequestDatetimeText(dateTimePrompt) 83 | .setRequestDateText(datePrompt) 84 | .setRequestTimeText(timePrompt)) 85 | } 86 | 87 | override val name: String 88 | get() = "actions.intent.DATETIME" 89 | 90 | override val parameters: Map 91 | get() { 92 | prepareMap() 93 | return map 94 | } 95 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/DeliveryAddress.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | import com.google.api.services.actions_fulfillment.v2.model.DeliveryAddressValueSpec 20 | import com.google.api.services.actions_fulfillment.v2.model.DeliveryAddressValueSpecAddressOptions 21 | 22 | class DeliveryAddress : HelperIntent { 23 | private val map: HashMap = HashMap() 24 | private var addressOptions: DeliveryAddressValueSpecAddressOptions? = null 25 | 26 | fun setAddressOptions(addressOptions: DeliveryAddressValueSpecAddressOptions): DeliveryAddress { 27 | this.addressOptions = addressOptions 28 | return this 29 | } 30 | 31 | override val name: String 32 | get() = "actions.intent.DELIVERY_ADDRESS" 33 | 34 | override val parameters: Map 35 | get() { 36 | prepareMap() 37 | map.put("@type", 38 | "type.googleapis.com/google.actions.v2.DeliveryAddressValueSpec") 39 | return map 40 | } 41 | 42 | private fun prepareMap() { 43 | val spec = DeliveryAddressValueSpec() 44 | spec.addressOptions = addressOptions 45 | 46 | spec.toMap(map) 47 | } 48 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/DigitalPurchaseCheck.kt: -------------------------------------------------------------------------------- 1 | package com.google.actions.api.response.helperintent 2 | 3 | import com.google.api.services.actions_fulfillment.v2.model.DigitalPurchaseCheckSpec 4 | 5 | /** 6 | * Helper intent to verify a user meets the necessary conditions for completing 7 | * a digital purchase. 8 | * 9 | * ``` Java 10 | * @ForIntent("digitalPurchaseCheck") 11 | * public ActionResponse digitalPurchaseCheck(ActionRequest request) { 12 | * ResponseBuilder responseBuilder = getResponseBuilder(); 13 | * responseBuilder 14 | * .add(new DigitalPurchaseCheck()); 15 | * return responseBuilder.build(); 16 | * } 17 | * ``` 18 | * 19 | * The following code demonstrates how to handle a digital purchase check result. 20 | * 21 | * ``` Java 22 | * @ForIntent("actions_intent_DIGITAL_PURCHASE_CHECK") 23 | * public ActionResponse handlePurchaseResponse(ActionRequest request) { 24 | * ResponseBuilder responseBuilder = getResponseBuilder(); 25 | * String checkResult = request.getArgument("DIGITAL_PURCHASE_CHECK_RESULT").getTextValue(); 26 | * if (checkResult.equalsIgnoreCase("CAN_PURCHASE")) { 27 | * // User is eligible to perform digital purchases. 28 | * responseBuilder 29 | * .add(new CompletePurchase() 30 | * .setSkuId(skuId) 31 | * .setDeveloperPayload("Optional developer payload string)); 32 | * } else if (checkResult.equalsIgnoreCase("CANNOT_PURCHASE")) { 33 | * // User does not meet necessary conditions for completing a digital 34 | * // purchase. This may be due to location, device or other factors. 35 | * responseBuilder.add("You are not eligible to perform this digital purchase.").endConversation(); 36 | * } else if (purchaseResult.equalsIgnoreCase("RESULT_TYPE_UNSPECIFIED")) { 37 | * responseBuilder.add("Digital purchase check failed. Do you want to try again?").endConversation(); 38 | * } else { 39 | * responseBuilder.add("There was an internal error. Please try again later").endConversation(); 40 | * } 41 | * return responseBuilder.build(); 42 | * } 43 | * ``` 44 | */ 45 | class DigitalPurchaseCheck : HelperIntent { 46 | private val map: HashMap = HashMap() 47 | 48 | override val name: String 49 | get() = "actions.intent.DIGITAL_PURCHASE_CHECK" 50 | 51 | override val parameters: Map 52 | get() { 53 | prepareMap() 54 | map.put("@type", 55 | "type.googleapis.com/google.actions.transactions.v3.DigitalPurchaseCheckSpec") 56 | return map 57 | } 58 | 59 | private fun prepareMap() { 60 | val spec = DigitalPurchaseCheckSpec() 61 | spec.toMap(map) 62 | } 63 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/HelperIntent.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | /** 20 | * Interface for objects that represent a system generated response. 21 | */ 22 | interface HelperIntent { 23 | val name: String 24 | val parameters: Map 25 | } 26 | -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/NewSurface.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | /** 20 | * Helper intent response to request user to transfer to another surface with 21 | * the specified capabilities. For example, the user may need to continue the 22 | * conversation on a device with a screen output. 23 | * 24 | * More details [here](https://developers.google.com/actions/assistant/surface-capabilities). 25 | * 26 | * Usage: 27 | * ``` Java 28 | * 29 | * ResponseBuilder responseBuilder = getResponseBuilder(); 30 | * responseBuilder.add(new NewSurface( 31 | * Capability.SCREEN_OUTPUT.getValue(), 32 | * "To show you an image", 33 | * "Check out this image")); 34 | * 35 | * ``` 36 | */ 37 | class NewSurface : HelperIntent { 38 | private val map = HashMap() 39 | 40 | private var capabilities: List? = null 41 | private var context: String? = null 42 | private var notificationTitle: String? = null 43 | 44 | fun setCapabilities(capabilities: List): NewSurface { 45 | this.capabilities = capabilities 46 | return this 47 | } 48 | 49 | fun setCapability(capability: String): NewSurface { 50 | this.capabilities = arrayOf(capability).asList() 51 | return this 52 | } 53 | 54 | fun setContext(context: String): NewSurface { 55 | this.context = context 56 | return this 57 | } 58 | 59 | fun setNotificationTitle(notificationTitle: String): NewSurface { 60 | this.notificationTitle = notificationTitle 61 | return this 62 | } 63 | 64 | private fun prepareMap() { 65 | map.put("@type", "type.googleapis.com/google.actions.v2.NewSurfaceValueSpec") 66 | map.put("capabilities", capabilities?.toTypedArray()) 67 | map.put("context", context) 68 | map.put("notificationTitle", notificationTitle) 69 | } 70 | 71 | override val name: String 72 | get() = "actions.intent.NEW_SURFACE" 73 | 74 | override val parameters: Map 75 | get() { 76 | prepareMap() 77 | return map 78 | } 79 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/Permission.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | import com.google.api.services.actions_fulfillment.v2.model.UpdatePermissionValueSpec 20 | 21 | /** 22 | * Helper intent response to request user for permissions. 23 | * Usage: 24 | * 25 | * ``` Java 26 | * ResponseBuilder responseBuilder = getResponseBuilder(); 27 | * responseBuilder 28 | * .add("Placeholder for permissions text") 29 | * .add(new Permission() 30 | * .setPermissions(new String[]{ 31 | * ConstantsKt.PERMISSION_NAME, 32 | * ConstantsKt.PERMISSION_DEVICE_PRECISE_LOCATION) 33 | * .setContext("To provide a better experience")); 34 | * ``` 35 | * 36 | * To get the user's response: 37 | * ``` Java 38 | * boolean permissionGranted = request.isPermissionGranted() != null && 39 | * request.isPermissionGranted().booleanValue(); 40 | * ``` 41 | */ 42 | open class Permission : HelperIntent { 43 | private val map = HashMap() 44 | 45 | private var permissions: Array? = null 46 | private var context: String? = null 47 | private var updatePermissionValueSpec: UpdatePermissionValueSpec? = null 48 | 49 | fun setPermissions(permissions: Array): Permission { 50 | this.permissions = permissions 51 | return this 52 | } 53 | 54 | fun setContext(context: String): Permission { 55 | this.context = context 56 | return this 57 | } 58 | 59 | protected fun setUpdatePermissionValueSpec( 60 | updatePermissionValueSpec: UpdatePermissionValueSpec): Permission { 61 | this.updatePermissionValueSpec = updatePermissionValueSpec 62 | return this 63 | } 64 | 65 | override val name: String 66 | get() = "actions.intent.PERMISSION" 67 | 68 | open fun prepareMap() { 69 | map.put("@type", 70 | "type.googleapis.com/google.actions.v2.PermissionValueSpec") 71 | map.put("optContext", context) 72 | map.put("permissions", permissions) 73 | map.put("updatePermissionValueSpec", updatePermissionValueSpec) 74 | } 75 | 76 | override val parameters: Map 77 | get() { 78 | prepareMap() 79 | return map 80 | } 81 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/Place.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | /** 20 | * Helper intent response to request user to provide a geo-located place, 21 | * possibly using contextual information, like a store near the user's location 22 | * or a contact's address. 23 | * 24 | * Developer provides custom text prompts to tailor the request handled by 25 | * Google. 26 | * 27 | * ``` Java 28 | * @ForIntent("askForPlace") 29 | * public CompletableFuture askForPlace(ActionRequest request) { 30 | * ResponseBuilder responseBuilder = getResponseBuilder(); 31 | * responseBuilder 32 | * .add("Placeholder for place text") 33 | * .add(new Place() 34 | * .setRequestPrompt("Where do you want to have lunch?"), 35 | * .setPermissionContext("To find lunch locations")); 36 | * return CompletableFuture.completedFuture(responseBuilder.build()); 37 | * } 38 | * ``` 39 | * 40 | * ``` Java 41 | * @ForIntent("actions_intent_place") 42 | * public CompletableFuture handlePlaceResponse(ActionRequest request) { 43 | * ResponseBuilder responseBuilder = getResponseBuilder(); 44 | * Location location = request.getPlace(); 45 | * String response; 46 | * if (location != null) { 47 | * response = " Suggested place - " + getLocationString(location); 48 | * } else { 49 | * response = "Unable to find any lunch locations"; 50 | * } 51 | * responseBuilder.add(response); 52 | * return CompletableFuture.completedFuture(responseBuilder.build()); 53 | * } 54 | * ``` 55 | */ 56 | class Place : HelperIntent { 57 | private val map = HashMap() 58 | 59 | private var requestPrompt: String? = null 60 | private var permissionContext: String? = null 61 | 62 | fun setRequestPrompt(requestPrompt: String): Place { 63 | this.requestPrompt = requestPrompt 64 | return this 65 | } 66 | 67 | fun setPermissionContext(permissionContext: String): Place { 68 | this.permissionContext = permissionContext 69 | return this 70 | } 71 | 72 | override val name: String 73 | get() = "actions.intent.PLACE" 74 | 75 | private fun prepareMap() { 76 | val extensionMap = HashMap() 77 | extensionMap.put("@type", "type.googleapis.com/google.actions.v2.PlaceValueSpec.PlaceDialogSpec") 78 | extensionMap.put("requestPrompt", requestPrompt) 79 | extensionMap.put("permissionContext", permissionContext) 80 | 81 | map.put("@type", "type.googleapis.com/google.actions.v2.PlaceValueSpec") 82 | map.put("dialog_spec", Extension(extensionMap)) 83 | } 84 | 85 | override val parameters: Map 86 | get() { 87 | prepareMap() 88 | return map 89 | } 90 | 91 | private inner class Extension(private val extension: Map) 92 | } 93 | -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/RegisterUpdate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | import com.google.api.services.actions_fulfillment.v2.model.Argument 20 | import com.google.api.services.actions_fulfillment.v2.model.RegisterUpdateValueSpec 21 | import com.google.api.services.actions_fulfillment.v2.model.TriggerContext 22 | import com.google.api.services.actions_fulfillment.v2.model.TriggerContextTimeContext 23 | 24 | /** 25 | * Helper intent to request for updates (eg: daily update). 26 | */ 27 | class RegisterUpdate : HelperIntent { 28 | private var map: MutableMap? = null 29 | private var registerValueSpec: RegisterUpdateValueSpec? = null 30 | 31 | private var intent: String? = null 32 | private var frequency: String = "DAILY" 33 | private var arguments: List? = null 34 | 35 | fun setIntent(intent: String): RegisterUpdate { 36 | this.intent = intent 37 | return this 38 | } 39 | 40 | fun setFrequency(frequency: String): RegisterUpdate { 41 | this.frequency = frequency 42 | return this 43 | } 44 | 45 | fun setArguments(arguments: List): RegisterUpdate { 46 | this.arguments = arguments 47 | return this 48 | } 49 | 50 | private fun prepareMap() { 51 | registerValueSpec = RegisterUpdateValueSpec() 52 | registerValueSpec!!.intent = intent 53 | registerValueSpec!!.arguments = arguments 54 | registerValueSpec!!.triggerContext = TriggerContext() 55 | registerValueSpec!!.triggerContext.timeContext = 56 | TriggerContextTimeContext().setFrequency(frequency) 57 | map = registerValueSpec?.toMutableMap()!! 58 | map?.put("@type", "type.googleapis.com/google.actions.v2.RegisterUpdateValueSpec") 59 | } 60 | 61 | override val name: String 62 | get() = "actions.intent.REGISTER_UPDATE" 63 | 64 | override val parameters: Map 65 | get() { 66 | prepareMap() 67 | return map?.toMap()!! 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/SelectionCarousel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | import com.google.api.services.actions_fulfillment.v2.model.CarouselSelect 20 | import com.google.api.services.actions_fulfillment.v2.model.CarouselSelectCarouselItem 21 | 22 | /** 23 | * Helper intent response to collect user's input with a carousel. 24 | * 25 | * Usage: 26 | * 27 | * ``` Java 28 | * List items = new ArrayList<>(); 29 | * CarouselSelectCarouselItem item; 30 | * for (int i = 0; i < 3; i++) { 31 | * item = new CarouselSelectCarouselItem(); 32 | * item.setTitle("Item #" + (i + 1)) 33 | * .setDescription("Description of Item #" + (i + 1)) 34 | * .setImage(new Image() 35 | * .setUrl(IMAGES[i]) 36 | * .setAccessibilityText("Image alt text")) 37 | * .setOptionInfo(new OptionInfo() 38 | * .setKey(String.valueOf(i + 1))); 39 | * items.add(item); 40 | * } 41 | * responseBuilder 42 | * .add("This is the first simple response for a selection carousel.") 43 | * .add(new SelectionCarousel().setItems(items)); 44 | * ``` 45 | * 46 | * The following code demonstrates how to get the user's selection: 47 | * 48 | * ``` Java 49 | * @ForIntent("item selected") 50 | * public CompletableFuture itemSelected(ActionRequest request) { 51 | * ResponseBuilder responseBuilder = getResponseBuilder(); 52 | * String selectedItem = request.getArgument("OPTION").getTextValue(); 53 | * responseBuilder 54 | * .add("You selected: " + selectedItem); 55 | * return CompletableFuture.completedFuture(responseBuilder.build()); 56 | * } 57 | * ``` 58 | */ 59 | class SelectionCarousel : HelperIntent { 60 | private val map = HashMap() 61 | private var items: List? = null 62 | 63 | fun setItems(items: List): SelectionCarousel { 64 | this.items = items 65 | return this 66 | } 67 | 68 | override val name: String 69 | get() = "actions.intent.OPTION" 70 | 71 | private fun prepareMap() { 72 | map.put("@type", "type.googleapis.com/google.actions.v2.OptionValueSpec") 73 | val carouselSelect = CarouselSelect() 74 | carouselSelect.items = items 75 | map.put("carouselSelect", carouselSelect) 76 | } 77 | 78 | override val parameters: Map 79 | get() { 80 | prepareMap() 81 | return map 82 | } 83 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/SelectionList.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | import com.google.api.services.actions_fulfillment.v2.model.ListSelect 20 | import com.google.api.services.actions_fulfillment.v2.model.ListSelectListItem 21 | 22 | /** 23 | * Helper intent response to collect user's input with a list. 24 | * 25 | * ``` Java 26 | * List items = new ArrayList<>(); 27 | * ListSelectListItem item; 28 | * for (int i = 0; i < 3; i++) { 29 | * item = new ListSelectListItem(); 30 | * item.setTitle("Item #" + (i + 1)) 31 | * .setDescription("Description of Item #" + (i + 1)) 32 | * .setImage(new Image() 33 | * .setUrl(IMAGES[i]) 34 | * .setAccessibilityText("Image alt text")) 35 | * .setOptionInfo(new OptionInfo() 36 | * .setKey(String.valueOf(i + 1))); 37 | * items.add(item); 38 | * } 39 | * responseBuilder 40 | * .add("This is the first simple response for a list.") 41 | * .add(new SelectionList().setTitle("List title").setItems(items)); 42 | * ``` 43 | * 44 | * The following code demonstrates how to get the user's selection: 45 | * 46 | * ``` Java 47 | * @ForIntent("item selected") 48 | * public CompletableFuture itemSelected(ActionRequest request) { 49 | * ResponseBuilder responseBuilder = getResponseBuilder(); 50 | * String selectedItem = request.getArgument("OPTION").getTextValue(); 51 | * responseBuilder 52 | * .add("You selected: " + selectedItem); 53 | * return CompletableFuture.completedFuture(responseBuilder.build()); 54 | * } 55 | * ``` 56 | */ 57 | class SelectionList : HelperIntent { 58 | private val map = HashMap() 59 | 60 | private var title: String? = null 61 | private var items: List? = null 62 | 63 | fun setTitle(title: String): SelectionList { 64 | this.title = title 65 | return this 66 | } 67 | 68 | fun setItems(items: List): SelectionList { 69 | this.items = items 70 | return this 71 | } 72 | 73 | override val name: String 74 | get() = "actions.intent.OPTION" 75 | 76 | private fun prepareMap() { 77 | map.put("@type", "type.googleapis.com/google.actions.v2.OptionValueSpec") 78 | val listSelect = ListSelect() 79 | listSelect.title = title 80 | listSelect.items = items 81 | map.put("listSelect", listSelect) 82 | } 83 | 84 | override val parameters: Map 85 | get() { 86 | prepareMap() 87 | return map 88 | } 89 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/SignIn.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | /** 20 | * Hands the user off to a web sign in flow. App sign in and OAuth credentials 21 | * are set in the [Actions console](https://console.actions.google.com). 22 | * 23 | * ``` Java 24 | * ResponseBuilder responseBuilder = getResponseBuilder(); 25 | * responseBuilder.add(new SignIn()); 26 | * ``` 27 | */ 28 | class SignIn : HelperIntent { 29 | private val map = HashMap() 30 | 31 | private var context: String? = null 32 | 33 | fun setContext(context: String): SignIn { 34 | this.context = context 35 | return this 36 | } 37 | 38 | private fun prepareMap() { 39 | map.put("@type", "type.googleapis.com/google.actions.v2.SignInValueSpec") 40 | map.put("optContext", context) 41 | } 42 | 43 | override val name: String 44 | get() = "actions.intent.SIGN_IN" 45 | 46 | override val parameters: Map 47 | get() { 48 | prepareMap() 49 | return map 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/TransactionDecision.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | import com.google.api.services.actions_fulfillment.v2.model.* 20 | 21 | class TransactionDecision : HelperIntent { 22 | private val map: HashMap = HashMap() 23 | private var orderOptions: OrderOptions? = null 24 | private var paymentOptions: PaymentOptions? = null 25 | private var presentationOptions: PresentationOptions? = null 26 | private var proposedOrder: ProposedOrder? = null 27 | 28 | fun setOrderOptions(orderOptions: OrderOptions): TransactionDecision { 29 | this.orderOptions = orderOptions 30 | return this 31 | } 32 | 33 | fun setPaymentOptions(paymentOptions: PaymentOptions): TransactionDecision { 34 | this.paymentOptions = paymentOptions 35 | return this 36 | } 37 | 38 | fun setPresentationOptions(presentationOptions: PresentationOptions): TransactionDecision { 39 | this.presentationOptions = presentationOptions 40 | return this 41 | } 42 | 43 | fun setProposedOrder(proposedOrder: ProposedOrder): TransactionDecision { 44 | this.proposedOrder = proposedOrder 45 | return this 46 | } 47 | 48 | override val name: String 49 | get() = "actions.intent.TRANSACTION_DECISION" 50 | 51 | override val parameters: Map 52 | get() { 53 | prepareMap() 54 | map.put("@type", 55 | "type.googleapis.com/google.actions.v2.TransactionDecisionValueSpec") 56 | return map 57 | } 58 | 59 | private fun prepareMap() { 60 | val spec = TransactionDecisionValueSpec() 61 | spec.orderOptions = orderOptions 62 | spec.paymentOptions = paymentOptions 63 | spec.presentationOptions = presentationOptions 64 | spec.proposedOrder = proposedOrder 65 | 66 | spec.toMap(map) 67 | } 68 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/TransactionRequirements.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | import com.google.api.services.actions_fulfillment.v2.model.OrderOptions 20 | import com.google.api.services.actions_fulfillment.v2.model.PaymentOptions 21 | import com.google.api.services.actions_fulfillment.v2.model.TransactionRequirementsCheckSpec 22 | 23 | class TransactionRequirements : HelperIntent { 24 | private val map: HashMap = HashMap() 25 | private var orderOptions: OrderOptions? = null 26 | private var paymentOptions: PaymentOptions? = null 27 | 28 | fun setOrderOptions(orderOptions: OrderOptions): TransactionRequirements { 29 | this.orderOptions = orderOptions 30 | return this 31 | } 32 | 33 | fun setPaymentOptions(paymentOptions: PaymentOptions): TransactionRequirements { 34 | this.paymentOptions = paymentOptions 35 | return this 36 | } 37 | 38 | override val name: String 39 | get() = "actions.intent.TRANSACTION_REQUIREMENTS_CHECK" 40 | 41 | override val parameters: Map 42 | get() { 43 | prepareMap() 44 | map.put("@type", 45 | "type.googleapis.com/google.actions.v2.TransactionRequirementsCheckSpec") 46 | return map 47 | } 48 | 49 | private fun prepareMap() { 50 | val spec = TransactionRequirementsCheckSpec() 51 | spec.orderOptions = orderOptions 52 | spec.paymentOptions = paymentOptions 53 | 54 | spec.toMap(map) 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/UpdatePermission.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent 18 | 19 | import com.google.api.services.actions_fulfillment.v2.model.Argument 20 | import com.google.api.services.actions_fulfillment.v2.model.UpdatePermissionValueSpec 21 | 22 | /** 23 | * Helper intent response to request user for permissions. 24 | * Usage: 25 | * 26 | * ``` Java 27 | * ResponseBuilder responseBuilder = getResponseBuilder(); 28 | * responseBuilder 29 | * .add("Placeholder text for update permission") 30 | * .add(new UpdatePermission().setIntent("intent_name")); 31 | * ``` 32 | * 33 | * To get the user's response: 34 | * ``` Java 35 | * boolean permissionGranted = request.isPermissionGranted() != null && 36 | * request.isPermissionGranted().booleanValue(); 37 | * ``` 38 | */ 39 | open class UpdatePermission : Permission() { 40 | 41 | private var intent: String? = null 42 | private var arguments: List? = null 43 | private var permissions: Array = Array(1, { "UPDATE" }) 44 | 45 | fun setIntent(intent: String): UpdatePermission { 46 | this.intent = intent 47 | return this 48 | } 49 | 50 | fun setArguments(arguments: List): UpdatePermission { 51 | this.arguments = arguments 52 | return this 53 | } 54 | 55 | override val name: String 56 | get() = "actions.intent.PERMISSION" 57 | 58 | override fun prepareMap() { 59 | setUpdatePermissionValueSpec(UpdatePermissionValueSpec() 60 | .setIntent(intent) 61 | .setArguments(arguments)) 62 | .setPermissions(permissions) 63 | super.prepareMap() 64 | } 65 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/transactions/v3/TransactionDecision.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent.transactions.v3 18 | 19 | import com.google.actions.api.response.helperintent.HelperIntent 20 | import com.google.api.services.actions_fulfillment.v2.model.* 21 | 22 | class TransactionDecision : HelperIntent { 23 | private val map: HashMap = HashMap() 24 | private var orderOptions: OrderOptionsV3? = null 25 | private var paymentParameters: PaymentParameters? = null 26 | private var presentationOptions: PresentationOptionsV3? = null 27 | private var order: OrderV3? = null 28 | 29 | fun setOrderOptions(orderOptions: OrderOptionsV3): TransactionDecision { 30 | this.orderOptions = orderOptions 31 | return this 32 | } 33 | 34 | fun setPaymentParameters(paymentParameters: PaymentParameters): TransactionDecision { 35 | this.paymentParameters = paymentParameters 36 | return this 37 | } 38 | 39 | fun setPresentationOptions(presentationOptions: PresentationOptionsV3): TransactionDecision { 40 | this.presentationOptions = presentationOptions 41 | return this 42 | } 43 | 44 | fun setOrder(order: OrderV3): TransactionDecision { 45 | this.order = order 46 | return this 47 | } 48 | 49 | override val name: String 50 | get() = "actions.intent.TRANSACTION_DECISION" 51 | 52 | override val parameters: Map 53 | get() { 54 | prepareMap() 55 | map.put("@type", 56 | "type.googleapis.com/google.actions.transactions.v3.TransactionDecisionValueSpec") 57 | return map 58 | } 59 | 60 | private fun prepareMap() { 61 | val spec = TransactionDecisionValueSpecV3() 62 | spec.orderOptions = orderOptions 63 | spec.paymentParameters = paymentParameters 64 | spec.presentationOptions = presentationOptions 65 | spec.order = order 66 | 67 | spec.toMap(map) 68 | } 69 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/response/helperintent/transactions/v3/TransactionRequirements.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.response.helperintent.transactions.v3 18 | 19 | import com.google.actions.api.response.helperintent.HelperIntent 20 | import com.google.api.services.actions_fulfillment.v2.model.TransactionRequirementsCheckSpecV3 21 | 22 | class TransactionRequirements : HelperIntent { 23 | private val map: HashMap = HashMap() 24 | 25 | override val name: String 26 | get() = "actions.intent.TRANSACTION_REQUIREMENTS_CHECK" 27 | 28 | override val parameters: Map 29 | get() { 30 | prepareMap() 31 | map.put("@type", 32 | "type.googleapis.com/google.actions.transactions.v3.TransactionRequirementsCheckSpec") 33 | return map 34 | } 35 | 36 | private fun prepareMap() { 37 | val spec = TransactionRequirementsCheckSpecV3() 38 | spec.toMap(map) 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/kotlin/com/google/actions/api/smarthome/SmartHomeApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.smarthome 18 | 19 | import com.google.actions.api.App 20 | import com.google.auth.oauth2.GoogleCredentials 21 | import com.google.home.graph.v1.HomeGraphApiServiceGrpc 22 | import com.google.home.graph.v1.HomeGraphApiServiceProto 23 | import io.grpc.ManagedChannelBuilder 24 | import io.grpc.auth.MoreCallCredentials 25 | import java.io.FileInputStream 26 | import java.util.concurrent.CompletableFuture 27 | import org.slf4j.LoggerFactory 28 | 29 | abstract class SmartHomeApp : App { 30 | var credentials: GoogleCredentials? = null 31 | 32 | constructor() 33 | 34 | constructor (credentials: GoogleCredentials) { 35 | this.credentials = credentials 36 | } 37 | 38 | constructor (fileName: String) { 39 | val stream = FileInputStream("key.json") 40 | this.credentials = GoogleCredentials.fromStream(stream) 41 | } 42 | 43 | private companion object { 44 | val LOG = LoggerFactory.getLogger(SmartHomeApp::class.java.name) 45 | } 46 | 47 | private fun homegraphWrapperDeprecationNotice(method: String): String { 48 | return "SmartHomeApp.$method homegraph wrapper method is deprecated. Use HomeGraph API Client Library for Java: https://github.com/googleapis/google-api-java-client-services/tree/master/clients/google-api-services-homegraph/v1" 49 | } 50 | 51 | /** 52 | * Builds a SmartHomeRequest object from a JSON-formatted string input. 53 | * 54 | * @param inputJson The request input as a string in a JSON format 55 | * @return A parsed request object 56 | */ 57 | fun createRequest(inputJson: String): SmartHomeRequest { 58 | return SmartHomeRequest.create(inputJson) 59 | } 60 | 61 | /** 62 | * The intent handler for action.devices.SYNC that is implemented in your smart home Action 63 | * 64 | * @param request The request object containing relevant fields 65 | * @param headers Request parameters 66 | * @return A valid response to the SYNC request 67 | */ 68 | abstract fun onSync(request: SyncRequest, headers: Map<*, *>?): SyncResponse 69 | 70 | /** 71 | * The intent handler for action.devices.QUERY that is implemented in your smart home Action 72 | * 73 | * @param request The request object containing relevant fields 74 | * @param headers Request parameters 75 | * @return A valid response to the QUERY request 76 | */ 77 | abstract fun onQuery(request: QueryRequest, headers: Map<*, *>?): QueryResponse 78 | 79 | /** 80 | * The intent handler for action.devices.EXECUTE that is implemented in your smart home Action 81 | * 82 | * @param request The request object containing relevant fields 83 | * @param headers Request parameters 84 | * @return A valid response to the EXECUTE request 85 | */ 86 | abstract fun onExecute(request: ExecuteRequest, headers: Map<*, *>?): ExecuteResponse 87 | 88 | /** 89 | * The intent handler for action.devices.DISCONNECT that is implemented in your smart home 90 | * Action. This intent does not expect a response. 91 | * 92 | * @param request The request object containing relevant fields 93 | * @param headers Request parameters 94 | */ 95 | abstract fun onDisconnect(request: DisconnectRequest, headers: Map<*, *>?): Unit 96 | 97 | /** 98 | * Sends a RequestSync command to the Home Graph, which will cause a SYNC request to be sent 99 | * to the server to refresh the list of a user's current devices. This should be called when 100 | * a user adds a new device, removes a device, or the device parameters change. 101 | * 102 | * @param agentUserId The user id for the given user on your service 103 | * @return A response to the API call 104 | */ 105 | @Deprecated(message = "Use HomeGraph API Client Library for Java: https://github.com/googleapis/google-api-java-client-services/tree/master/clients/google-api-services-homegraph/v1") 106 | fun requestSync(agentUserId: String): HomeGraphApiServiceProto.RequestSyncDevicesResponse { 107 | LOG.warn(homegraphWrapperDeprecationNotice("::requestSync.name")); 108 | if (this.credentials == null) { 109 | throw IllegalArgumentException("You must pass credentials in the app constructor") 110 | } 111 | val channel = ManagedChannelBuilder.forTarget("homegraph.googleapis.com").build() 112 | 113 | val blockingStub = HomeGraphApiServiceGrpc.newBlockingStub(channel) 114 | // See https://grpc.io/docs/guides/auth.html#authenticate-with-google-3. 115 | .withCallCredentials(MoreCallCredentials.from(this.credentials)) 116 | val request = HomeGraphApiServiceProto.RequestSyncDevicesRequest.newBuilder() 117 | .setAgentUserId(agentUserId) 118 | .build() 119 | 120 | return blockingStub.requestSyncDevices(request) 121 | 122 | } 123 | 124 | /** 125 | * Sends a ReportState command to the Home Graph, which will store a device's current state. 126 | * This should be called after a device receives an EXECUTE request, or if the device has 127 | * changed state through a means outside of your smart home Action. 128 | * 129 | * @param request A payload containing a series of devices and their connected states 130 | * @return A response to the API call 131 | */ 132 | @Deprecated(message = "Use HomeGraph API Client Library for Java: https://github.com/googleapis/google-api-java-client-services/tree/master/clients/google-api-services-homegraph/v1") 133 | fun reportState(request: HomeGraphApiServiceProto.ReportStateAndNotificationRequest): 134 | HomeGraphApiServiceProto.ReportStateAndNotificationResponse { 135 | LOG.warn(homegraphWrapperDeprecationNotice(::reportState.name)); 136 | if (this.credentials == null) { 137 | throw IllegalArgumentException("You must pass credentials in the app constructor") 138 | } 139 | val channel = ManagedChannelBuilder.forTarget("homegraph.googleapis.com").build() 140 | 141 | val blockingStub = HomeGraphApiServiceGrpc.newBlockingStub(channel) 142 | // See https://grpc.io/docs/guides/auth.html#authenticate-with-google-3. 143 | .withCallCredentials(MoreCallCredentials.from(this.credentials)) 144 | 145 | return blockingStub.reportStateAndNotification(request) 146 | 147 | } 148 | 149 | override fun handleRequest(inputJson: String?, headers: Map<*, *>?): CompletableFuture { 150 | if (inputJson == null || inputJson.isEmpty()) { 151 | return handleError("Invalid or empty JSON") 152 | } 153 | 154 | return try { 155 | val request = createRequest(inputJson) 156 | val response = routeRequest(request, headers) 157 | 158 | val future: CompletableFuture = CompletableFuture() 159 | future.complete(response) 160 | future.thenApply { this.getAsJson(it) } 161 | .exceptionally { throwable -> throwable.message } 162 | } catch (e: Exception) { 163 | handleError(e) 164 | } 165 | } 166 | 167 | @Throws(Exception::class) 168 | private fun routeRequest(request: SmartHomeRequest, headers: Map<*, *>?): SmartHomeResponse { 169 | when (request.javaClass) { 170 | SyncRequest::class.java -> { 171 | return onSync(request as SyncRequest, headers) 172 | } 173 | QueryRequest::class.java -> { 174 | return onQuery(request as QueryRequest, headers) 175 | } 176 | ExecuteRequest::class.java -> { 177 | return onExecute(request as ExecuteRequest, headers) 178 | } 179 | DisconnectRequest::class.java -> { 180 | onDisconnect(request as DisconnectRequest, headers) 181 | return SmartHomeResponse() 182 | } 183 | else -> { 184 | // Unable to find a method with the annotation matching the intent. 185 | throw Exception("Intent handler not found - ${request.inputs[0].intent}") 186 | } 187 | } 188 | } 189 | 190 | private fun handleError(exception: Exception): CompletableFuture { 191 | exception.printStackTrace() 192 | return handleError(exception.message) 193 | } 194 | 195 | private fun handleError(message: String?): CompletableFuture { 196 | val future = CompletableFuture() 197 | future.completeExceptionally(Exception(message)) 198 | return future 199 | } 200 | 201 | private fun getAsJson(response: SmartHomeResponse): String { 202 | return response.build().toString() 203 | } 204 | } -------------------------------------------------------------------------------- /src/main/main.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 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 | -------------------------------------------------------------------------------- /src/main/proto/google/api/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.api; 18 | 19 | import "google/api/http.proto"; 20 | import "google/protobuf/descriptor.proto"; 21 | 22 | option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; 23 | option java_multiple_files = true; 24 | option java_outer_classname = "AnnotationsProto"; 25 | option java_package = "com.google.api"; 26 | option objc_class_prefix = "GAPI"; 27 | 28 | extend google.protobuf.MethodOptions { 29 | // See `HttpRule`. 30 | HttpRule http = 72295728; 31 | } -------------------------------------------------------------------------------- /src/main/proto/google/home/graph/v1/device.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.home.graph.v1; 18 | 19 | import "google/protobuf/struct.proto"; 20 | 21 | option go_package = "google.golang.org/genproto/googleapis/home/graph/v1;graph"; 22 | option java_outer_classname = "DeviceProto"; 23 | option java_package = "com.google.home.graph.v1"; 24 | 25 | 26 | // Third-party partner's device definition. 27 | message Device { 28 | // Third-party partner's device ID. 29 | string id = 1; 30 | 31 | // Hardware type of the device (e.g. light, outlet, etc). 32 | string type = 2; 33 | 34 | // Traits supported by the device. 35 | repeated string traits = 3; 36 | 37 | // Name of the device given by the third party. This includes names given to 38 | // the device via third party device manufacturer's app, model names for the 39 | // device, etc. 40 | DeviceNames name = 4; 41 | 42 | // Indicates whether the state of this device is being reported to Google 43 | // through ReportStateAndNotification call. 44 | bool will_report_state = 5; 45 | 46 | // If the third-party partner's cloud configuration includes placing devices 47 | // in rooms, the name of the room can be provided here. 48 | string room_hint = 6; 49 | 50 | // As in roomHint, for structures that users set up in the partner's system. 51 | string structure_hint = 7; 52 | 53 | // Device manufacturer, model, hardware version, and software version. 54 | DeviceInfo device_info = 8; 55 | 56 | // Attributes for the traits supported by the device. 57 | google.protobuf.Struct attributes = 9; 58 | 59 | // Custom JSON data provided by the manufacturer and attached to QUERY and 60 | // EXECUTE requests in AoG. 61 | google.protobuf.Struct custom_data = 10; 62 | 63 | // IDs of other devices associated with this device. This is used to 64 | // represent a device group (e.g. bonded zone) or "facets" synced 65 | // through different flows (e.g. Google Nest Hub Max with a Nest Camera). 66 | // 67 | // This may also be used to pass in alternate IDs used to identify a cloud 68 | // synced device for local execution (i.e. local verification). If used for 69 | // local verification, this field is synced from the cloud. 70 | repeated AgentOtherDeviceId other_device_ids = 11; 71 | } 72 | 73 | // Different names for the device. 74 | message DeviceNames { 75 | // Primary name of the device, generally provided by the user. 76 | string name = 1; 77 | 78 | // Additional names provided by the user for the device. 79 | repeated string nicknames = 2; 80 | 81 | // List of names provided by the partner rather than the user, often 82 | // manufacturer names, SKUs, etc. 83 | repeated string default_names = 3; 84 | } 85 | 86 | // Device information. 87 | message DeviceInfo { 88 | // Device manufacturer. 89 | string manufacturer = 1; 90 | 91 | // Device model. 92 | string model = 2; 93 | 94 | // Device hardware version. 95 | string hw_version = 3; 96 | 97 | // Device software version. 98 | string sw_version = 4; 99 | } 100 | 101 | // Identifies a device in the third party or first party system. 102 | message AgentOtherDeviceId { 103 | // The agent's ID. Generally it is the agent's AoG project id. 104 | string agent_id = 1; 105 | 106 | // Device ID defined by the agent. The device_id must be unique. 107 | string device_id = 2; 108 | } -------------------------------------------------------------------------------- /src/main/resources/metadata.properties: -------------------------------------------------------------------------------- 1 | version=v1.8.0 -------------------------------------------------------------------------------- /src/test/kotlin/com/google/actions/api/DialogflowRequestTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api 18 | 19 | import com.google.actions.api.impl.DialogflowRequest 20 | import com.google.actions.api.response.ResponseBuilder 21 | import com.google.gson.Gson 22 | import com.google.gson.JsonObject 23 | import org.junit.Assert.assertEquals 24 | import org.junit.Assert.assertNotNull 25 | import org.slf4j.LoggerFactory 26 | import org.testng.annotations.Test 27 | import java.io.IOException 28 | import java.net.URISyntaxException 29 | import java.nio.file.Files 30 | import java.nio.file.Paths 31 | import java.util.concurrent.CompletableFuture 32 | 33 | class DialogflowRequestTest { 34 | 35 | private companion object { 36 | val LOG = LoggerFactory.getLogger(DialogflowRequestTest::class.java.name) 37 | } 38 | 39 | @Throws(IOException::class) 40 | private fun fromFile(file: String): DialogflowRequest { 41 | val absolutePath = Paths.get("src", "test", "resources", 42 | file) 43 | val gson = Gson() 44 | val reader = Files.newBufferedReader(absolutePath) 45 | val json = gson.fromJson(reader, JsonObject::class.java) 46 | 47 | return DialogflowRequest.create(json, null) 48 | } 49 | 50 | @Throws(IOException::class, URISyntaxException::class) 51 | private fun readFromFile(fileName: String): String { 52 | val resource = DialogflowRequestTest::class.java.classLoader 53 | .getResource(fileName) 54 | val path = Paths.get(resource!!.toURI()) 55 | return String(Files.readAllBytes(path)) 56 | } 57 | 58 | @Test 59 | @Throws(Exception::class) 60 | fun welcomeIntentJsonIsParsed() { 61 | val request = fromFile("dialogflow_welcome.json") 62 | assertNotNull(request.webhookRequest) 63 | assertEquals("Default Welcome Intent", request.intent) 64 | } 65 | 66 | @Test 67 | @Throws(Exception::class) 68 | fun jsonWithGooglePayloadIsParsed() { 69 | val dialogflowRequest = fromFile( 70 | "dialogflow_complete.json") 71 | val aogRequest = dialogflowRequest.aogRequest 72 | assertNotNull(dialogflowRequest.webhookRequest) 73 | assertNotNull(aogRequest) 74 | assertEquals("actions.intent.TEXT", aogRequest!!.intent) 75 | assertEquals("favorite fake color", dialogflowRequest.intent) 76 | 77 | val webhookRequest = dialogflowRequest.webhookRequest 78 | assertEquals("acdddd", webhookRequest.responseId) 79 | 80 | val queryResult = webhookRequest.queryResult 81 | assertEquals("blue grey coffee", queryResult.queryText) 82 | assertEquals("en-us", queryResult.languageCode) 83 | assertEquals("blue grey coffee", 84 | queryResult.parameters["fakeColor"]) 85 | 86 | val contexts = queryResult.outputContexts 87 | assertEquals(7, contexts.size.toLong()) 88 | assertEquals("Yellow", 89 | contexts[0].parameters["color.original"]) 90 | } 91 | 92 | @Test 93 | @Throws(Exception::class) 94 | fun testPackageEntitlements() { 95 | val dialogflowRequest = fromFile( 96 | "dialogflow_package_entitlements.json") 97 | val aogRequest = dialogflowRequest.aogRequest 98 | assertNotNull(dialogflowRequest.webhookRequest) 99 | assertNotNull(aogRequest) 100 | val packageEntitlements = dialogflowRequest.user?.packageEntitlements 101 | assertEquals(1, packageEntitlements?.size) 102 | val packageEntitlement = dialogflowRequest.user?.packageEntitlements?.get(0)!! 103 | assertEquals("package.name", packageEntitlement.packageName) 104 | val entitlements = packageEntitlement.entitlements 105 | assertNotNull(entitlements) 106 | assertEquals(2, entitlements!!.size) 107 | 108 | for (entitlement in entitlements) { 109 | assertEquals("sku", entitlement.sku) 110 | assertEquals("IN_APP", entitlement.skuType) 111 | val inAppDetails = entitlement.inAppDetails 112 | assertNotNull(inAppDetails) 113 | assertEquals("signature", inAppDetails.inAppDataSignature) 114 | val inAppPurchaseData = inAppDetails.inAppPurchaseData 115 | assertNotNull(inAppPurchaseData) 116 | assertEquals("purchaseToken", inAppPurchaseData["purchaseToken"]) 117 | assertEquals("productId", inAppPurchaseData["productId"]) 118 | assertEquals("orderId", inAppPurchaseData["orderId"]) 119 | assertEquals(1557772151801, (inAppPurchaseData["purchaseTime"] as Number).toLong()) 120 | assertEquals("package.name", inAppPurchaseData["packageName"]) 121 | assertEquals(0, (inAppPurchaseData["purchaseState"] as Number).toInt()) 122 | } 123 | } 124 | 125 | @Test 126 | @Throws(Exception::class) 127 | fun conversationDataIsParsed() { 128 | val dialogflowRequest = fromFile( 129 | "dialogflow_complete.json") 130 | val conversationData = dialogflowRequest.conversationData 131 | assertEquals("first last", conversationData["userName"]) 132 | } 133 | 134 | @Test 135 | @Throws(Exception::class) 136 | fun conversationDataIsParsed2() { 137 | val request = fromFile("dialogflow_with_conv_data.json") 138 | val convData = request.conversationData 139 | val history = convData["history"] as ArrayList 140 | val headquarters = convData["headquarters"] as ArrayList 141 | val cats = convData["cats"] as ArrayList 142 | assertNotNull(request.webhookRequest) 143 | assertEquals("google_headquarters_fact_1", headquarters[0]) 144 | assertEquals("cat_fact_2", cats[1]) 145 | assertEquals(0, history.size) 146 | } 147 | 148 | @Test 149 | @Throws(Exception::class) 150 | fun intentHandlerIsInvoked() { 151 | val app = MyDialogflowApp() 152 | val inputJson = Files.readAllLines( 153 | Paths.get("src", "test", "resources", 154 | "dialogflow_welcome.json")).joinToString("\n") 155 | app.handleRequest(inputJson, null) 156 | } 157 | 158 | internal inner class MyDialogflowApp : DialogflowApp() { 159 | 160 | @ForIntent("Default Welcome Intent") 161 | fun handleFooIntent( 162 | request: ActionRequest): CompletableFuture { 163 | LOG.info("handleFooIntent is invoked.") 164 | val responseBuilder = getResponseBuilder(request) 165 | 166 | return CompletableFuture.completedFuture( 167 | responseBuilder.build()) 168 | } 169 | } 170 | 171 | @Test 172 | @Throws(Exception::class) 173 | fun conversationDataIsMutable() { 174 | val request = fromFile("dialogflow_with_conv_data.json") 175 | var conversationDataValue = request.conversationData["test"] as String 176 | assertEquals("hello", conversationDataValue) 177 | request.conversationData["test"] = "world" 178 | conversationDataValue = request.conversationData["test"] as String 179 | assertEquals("world", conversationDataValue) 180 | } 181 | 182 | @Test 183 | @Throws(Exception::class) 184 | fun userStorageIsMutable() { 185 | val request = fromFile("dialogflow_user_storage.json") 186 | var userStorageValue = request.userStorage["test"] as String 187 | assertEquals("hello", userStorageValue) 188 | request.userStorage["test"] = "world" 189 | userStorageValue = request.userStorage["test"] as String 190 | assertEquals("world", userStorageValue) 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/test/kotlin/com/google/actions/api/smarthome/HomeGraphTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.smarthome 18 | 19 | import com.google.auth.oauth2.AccessToken 20 | import com.google.auth.oauth2.GoogleCredentials 21 | import com.google.home.graph.v1.HomeGraphApiServiceGrpc 22 | import com.google.home.graph.v1.HomeGraphApiServiceProto 23 | import com.google.protobuf.Struct 24 | import com.google.protobuf.Value 25 | import io.grpc.ManagedChannel 26 | import io.grpc.inprocess.InProcessChannelBuilder 27 | import io.grpc.inprocess.InProcessServerBuilder 28 | import io.grpc.stub.StreamObserver 29 | import io.grpc.testing.GrpcCleanupRule 30 | import junit.framework.Assert.assertEquals 31 | import org.junit.Before 32 | import org.junit.Rule 33 | import org.junit.Test 34 | import org.junit.runner.RunWith 35 | import org.junit.runners.JUnit4 36 | import org.mockito.AdditionalAnswers.delegatesTo 37 | import org.mockito.ArgumentCaptor 38 | import org.mockito.Matchers 39 | import org.mockito.Mockito.mock 40 | import org.mockito.Mockito.verify 41 | import java.util.* 42 | 43 | @RunWith(JUnit4::class) 44 | class HomeGraphTest { 45 | private lateinit var credentials: GoogleCredentials 46 | 47 | class HomeGraphTestApp : TestSmartHomeApp() { 48 | override fun onSync(request: SyncRequest, headers: Map<*, *>?): SyncResponse { 49 | TODO("not implemented") 50 | } 51 | 52 | override fun onQuery(request: QueryRequest, headers: Map<*, *>?): QueryResponse { 53 | TODO("not implemented") 54 | } 55 | 56 | override fun onExecute(request: ExecuteRequest, headers: Map<*, *>?): ExecuteResponse { 57 | TODO("not implemented") 58 | } 59 | 60 | override fun onDisconnect(request: DisconnectRequest, headers: Map<*, *>?): Unit { 61 | TODO("not implemented") 62 | } 63 | } 64 | 65 | @get:Rule 66 | val grpcCleanup = GrpcCleanupRule() 67 | 68 | val serviceImpl: HomeGraphApiServiceGrpc.HomeGraphApiServiceImplBase = 69 | mock(HomeGraphApiServiceGrpc.HomeGraphApiServiceImplBase::class.java, 70 | delegatesTo( 71 | object : HomeGraphApiServiceGrpc.HomeGraphApiServiceImplBase() { 72 | override fun requestSyncDevices( 73 | request: HomeGraphApiServiceProto.RequestSyncDevicesRequest?, 74 | responseObserver: StreamObserver< 75 | HomeGraphApiServiceProto.RequestSyncDevicesResponse>?) { 76 | responseObserver!!.onNext( 77 | HomeGraphApiServiceProto.RequestSyncDevicesResponse.getDefaultInstance()) 78 | responseObserver.onCompleted() 79 | } 80 | 81 | override fun reportStateAndNotification( 82 | request: HomeGraphApiServiceProto.ReportStateAndNotificationRequest?, 83 | responseObserver: StreamObserver< 84 | HomeGraphApiServiceProto.ReportStateAndNotificationResponse>?) { 85 | responseObserver!!.onNext( 86 | HomeGraphApiServiceProto.ReportStateAndNotificationResponse.getDefaultInstance()) 87 | responseObserver.onCompleted() 88 | } 89 | })) 90 | 91 | lateinit var app: HomeGraphTestApp 92 | lateinit var chan: ManagedChannel 93 | 94 | @Before 95 | fun generateMockCredentials() { 96 | val expirationDate = Date(Date().time + 1000 * 60 * 60) // Now + 1 hour 97 | credentials = GoogleCredentials.create( 98 | AccessToken("sample-access-token", expirationDate)) 99 | 100 | val serverName: String = InProcessServerBuilder.generateName() 101 | grpcCleanup.register(InProcessServerBuilder.forName(serverName) 102 | .directExecutor() 103 | .addService(serviceImpl) 104 | .build() 105 | .start()) 106 | 107 | chan = grpcCleanup.register(InProcessChannelBuilder.forName(serverName) 108 | .directExecutor() 109 | .build()) 110 | 111 | app = HomeGraphTestApp() 112 | } 113 | 114 | @Test 115 | fun testRequestSync() { 116 | val captor = ArgumentCaptor.forClass( 117 | HomeGraphApiServiceProto.RequestSyncDevicesRequest::class.java) 118 | 119 | app.requestSyncTest("123", chan) 120 | verify(serviceImpl).requestSyncDevices(captor.capture(), 121 | Matchers.any>()) 122 | 123 | 124 | assertEquals("123", captor.value.agentUserId) 125 | } 126 | 127 | @Test 128 | fun testReportState() { 129 | val captor: ArgumentCaptor = 130 | ArgumentCaptor.forClass( 131 | HomeGraphApiServiceProto.ReportStateAndNotificationRequest::class.java) 132 | 133 | val colorRed = 16711680.0 134 | val colorSpectrum = Struct.newBuilder() 135 | .putFields("name", Value.newBuilder().setStringValue("Red").build()) 136 | .putFields("spectrumRgb", Value.newBuilder().setNumberValue(colorRed).build()) 137 | .build() 138 | val deviceStates = Value.newBuilder().setStructValue( 139 | Struct.newBuilder() 140 | .putFields("color", 141 | Value.newBuilder().setStructValue(colorSpectrum).build()) 142 | .build() 143 | ).build() 144 | val requestStates = Struct.newBuilder().putFields("device1", deviceStates).build() 145 | 146 | app.reportStateTest(HomeGraphApiServiceProto.ReportStateAndNotificationRequest.newBuilder() 147 | .setAgentUserId("123") 148 | .setRequestId("ff36a3cc-ec34-11e6-b1a0-64510650abcf") 149 | .setPayload(HomeGraphApiServiceProto.StateAndNotificationPayload.newBuilder() 150 | .setDevices(HomeGraphApiServiceProto.ReportStateAndNotificationDevice 151 | .newBuilder() 152 | .setStates(requestStates) 153 | .build()) 154 | .build()) 155 | .build(), chan) 156 | verify(serviceImpl).reportStateAndNotification(captor.capture(), 157 | Matchers.any>()) 159 | 160 | 161 | assertEquals("123", captor.value.agentUserId) 162 | assertEquals(requestStates, captor.value.payload.devices.states) 163 | } 164 | 165 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/google/actions/api/smarthome/SmartHomeRequestTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.smarthome 18 | 19 | import com.google.gson.Gson 20 | import com.google.gson.JsonObject 21 | import org.junit.Assert 22 | import org.junit.Test 23 | import java.io.IOException 24 | import java.nio.file.Files 25 | import java.nio.file.Paths 26 | 27 | class SmartHomeRequestTest { 28 | 29 | @Throws(IOException::class) 30 | private fun fromFile(file: String): SmartHomeRequest { 31 | val absolutePath = Paths.get("src", "test", "resources", 32 | file) 33 | val gson = Gson() 34 | val reader = Files.newBufferedReader(absolutePath) 35 | val json = gson.fromJson(reader, JsonObject::class.java) 36 | 37 | return SmartHomeRequest.create(json.toString()) 38 | } 39 | 40 | @Test 41 | @Throws(Exception::class) 42 | fun basicSyncJsonIsParsed() { 43 | val request = fromFile("smarthome_sync_request.json") as SyncRequest 44 | Assert.assertNotNull(request) 45 | Assert.assertNotNull(request.requestId) 46 | Assert.assertEquals(request.inputs.size, 1) 47 | Assert.assertEquals(request.inputs[0].intent, "action.devices.SYNC") 48 | } 49 | 50 | @Test 51 | @Throws(Exception::class) 52 | fun basicQueryJsonIsParsed() { 53 | val request = fromFile("smarthome_query_request.json") as QueryRequest 54 | Assert.assertNotNull(request) 55 | Assert.assertNotNull(request.requestId) 56 | Assert.assertEquals(request.inputs.size, 1) 57 | Assert.assertEquals(request.inputs[0].intent, "action.devices.QUERY") 58 | 59 | val payload = (request.inputs[0] as QueryRequest.Inputs).payload 60 | Assert.assertEquals(payload.devices.size, 2) 61 | Assert.assertEquals(payload.devices[0].id, "123") 62 | 63 | Assert.assertEquals(payload.devices[1].id, "456") 64 | } 65 | 66 | @Test 67 | @Throws(Exception::class) 68 | fun customDataQueryJsonIsParsed() { 69 | val request = fromFile("smarthome_query_customdata_request.json") as QueryRequest 70 | Assert.assertNotNull(request) 71 | Assert.assertNotNull(request.requestId) 72 | Assert.assertEquals(request.inputs.size, 1) 73 | Assert.assertEquals(request.inputs[0].intent, "action.devices.QUERY") 74 | 75 | val payload = (request.inputs[0] as QueryRequest.Inputs).payload 76 | Assert.assertEquals(payload.devices.size, 2) 77 | Assert.assertEquals(payload.devices[0].id, "123") 78 | Assert.assertEquals(payload.devices[0].customData!!["fooValue"], 74) 79 | 80 | Assert.assertEquals(payload.devices[1].id, "456") 81 | Assert.assertEquals(payload.devices[1].customData!!["fooValue"], 12) 82 | } 83 | 84 | @Test 85 | @Throws(Exception::class) 86 | fun basicExecuteJsonIsParsed() { 87 | val request = fromFile("smarthome_execute_request.json") as ExecuteRequest 88 | Assert.assertNotNull(request) 89 | Assert.assertNotNull(request.requestId) 90 | Assert.assertEquals(request.inputs.size, 1) 91 | Assert.assertEquals(request.inputs[0].intent, "action.devices.EXECUTE") 92 | 93 | val payload = (request.inputs[0] as ExecuteRequest.Inputs).payload 94 | Assert.assertEquals(payload.commands.size, 1) 95 | Assert.assertEquals(payload.commands[0].devices.size, 2) 96 | Assert.assertEquals(payload.commands[0].devices[0].id, "123") 97 | Assert.assertEquals(payload.commands[0].devices[1].id, "456") 98 | Assert.assertEquals(payload.commands[0].execution.size, 1) 99 | Assert.assertEquals(payload.commands[0].execution[0].command, "action.devices.commands.OnOff") 100 | Assert.assertEquals(payload.commands[0].execution[0].params!!["on"], true) 101 | } 102 | 103 | @Test 104 | @Throws(Exception::class) 105 | fun twoFactorExecuteJsonIsParsed() { 106 | val request = fromFile("smarthome_execute_2fa_request.json") as ExecuteRequest 107 | Assert.assertNotNull(request) 108 | Assert.assertNotNull(request.requestId) 109 | Assert.assertEquals(request.inputs.size, 1) 110 | Assert.assertEquals(request.inputs[0].intent, "action.devices.EXECUTE") 111 | 112 | val payload = (request.inputs[0] as ExecuteRequest.Inputs).payload 113 | Assert.assertEquals(payload.commands[0].execution.size, 2) 114 | Assert.assertEquals(payload.commands[0].execution[0].challenge!!["pin"], "333222") 115 | Assert.assertEquals(payload.commands[0].execution[1].challenge!!["ack"], true) 116 | } 117 | 118 | @Test 119 | @Throws(Exception::class) 120 | fun customDataExecuteJsonIsParsed() { 121 | val request = fromFile("smarthome_execute_customdata_request.json") as ExecuteRequest 122 | Assert.assertNotNull(request) 123 | Assert.assertNotNull(request.requestId) 124 | Assert.assertEquals(request.inputs.size, 1) 125 | Assert.assertEquals(request.inputs[0].intent, "action.devices.EXECUTE") 126 | 127 | val payload = (request.inputs[0] as ExecuteRequest.Inputs).payload 128 | Assert.assertEquals(payload.commands.size, 1) 129 | Assert.assertEquals(payload.commands[0].devices.size, 2) 130 | Assert.assertEquals(payload.commands[0].devices[0].id, "123") 131 | Assert.assertEquals(payload.commands[0].devices[0].customData!!["fooValue"], 74) 132 | Assert.assertEquals(payload.commands[0].devices[1].id, "456") 133 | Assert.assertEquals(payload.commands[0].devices[1].customData!!["fooValue"], 36) 134 | Assert.assertEquals(payload.commands[0].execution.size, 1) 135 | Assert.assertEquals(payload.commands[0].execution[0].command, "action.devices.commands.OnOff") 136 | Assert.assertEquals(payload.commands[0].execution[0].params!!["on"], true) 137 | } 138 | 139 | @Test 140 | @Throws(Exception::class) 141 | fun dockExecuteJsonIsParsed() { 142 | val request = fromFile("smarthome_execute_dock_request.json") as ExecuteRequest 143 | Assert.assertNotNull(request) 144 | Assert.assertNotNull(request.requestId) 145 | Assert.assertEquals(request.inputs.size, 1) 146 | Assert.assertEquals(request.inputs[0].intent, "action.devices.EXECUTE") 147 | 148 | val payload = (request.inputs[0] as ExecuteRequest.Inputs).payload 149 | Assert.assertEquals(payload.commands.size, 1) 150 | Assert.assertEquals(payload.commands[0].devices.size, 1) 151 | Assert.assertEquals(payload.commands[0].devices[0].id, "vacuumJawn") 152 | Assert.assertEquals(payload.commands[0].execution.size, 1) 153 | Assert.assertEquals(payload.commands[0].execution[0].command, "action.devices.commands.Dock") 154 | } 155 | 156 | @Test 157 | @Throws(Exception::class) 158 | fun basicDisconnectJsonIsParsed() { 159 | val request = fromFile("smarthome_disconnect_request.json") as DisconnectRequest 160 | Assert.assertNotNull(request) 161 | Assert.assertNotNull(request.requestId) 162 | Assert.assertEquals(request.inputs.size, 1) 163 | Assert.assertEquals(request.inputs[0].intent, "action.devices.DISCONNECT") 164 | } 165 | 166 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/google/actions/api/smarthome/TestSmartHomeApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.smarthome 18 | 19 | import com.google.common.annotations.VisibleForTesting 20 | import com.google.home.graph.v1.HomeGraphApiServiceGrpc 21 | import com.google.home.graph.v1.HomeGraphApiServiceProto 22 | import com.google.home.graph.v1.HomeGraphApiServiceProto.ReportStateAndNotificationRequest 23 | import io.grpc.ManagedChannel 24 | 25 | 26 | abstract class TestSmartHomeApp : SmartHomeApp() { 27 | @VisibleForTesting 28 | fun requestSyncTest(agentUserId: String, chan: ManagedChannel): 29 | HomeGraphApiServiceProto.RequestSyncDevicesResponse { 30 | val blockingStub = HomeGraphApiServiceGrpc.newBlockingStub(chan) 31 | 32 | val request = HomeGraphApiServiceProto.RequestSyncDevicesRequest.newBuilder() 33 | .setAgentUserId(agentUserId) 34 | .build() 35 | return blockingStub.requestSyncDevices(request) 36 | } 37 | 38 | @VisibleForTesting 39 | fun reportStateTest(request: ReportStateAndNotificationRequest, chan: ManagedChannel): 40 | HomeGraphApiServiceProto.ReportStateAndNotificationResponse { 41 | val blockingStub = HomeGraphApiServiceGrpc.newBlockingStub(chan) 42 | 43 | return blockingStub.reportStateAndNotification(request) 44 | } 45 | } -------------------------------------------------------------------------------- /src/test/kotlin/com/google/actions/api/test/MockRequestBuilderTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.actions.api.test 18 | 19 | import junit.framework.TestCase.* 20 | import org.testng.annotations.Test 21 | 22 | class MockRequestBuilderTest { 23 | 24 | @Test 25 | fun testBasicAogRequest() { 26 | val aogRequest = MockRequestBuilder 27 | .welcome("welcome", false) 28 | .build() 29 | assertEquals("welcome", aogRequest.intent) 30 | assertNotNull(aogRequest.appRequest?.conversation) 31 | } 32 | 33 | @Test 34 | fun testBasicDialogflowRequest() { 35 | val dialogflowRequest = MockRequestBuilder 36 | .welcome("welcome") 37 | .build() 38 | assertEquals("welcome", dialogflowRequest.intent) 39 | assertNotNull(dialogflowRequest.webhookRequest?.originalDetectIntentRequest?.payload) 40 | } 41 | 42 | @Test 43 | fun testConfirmationResponse() { 44 | var request = MockRequestBuilder 45 | .userConfirmation() 46 | .build() 47 | assertTrue(request.getUserConfirmation()) 48 | 49 | request = MockRequestBuilder 50 | .userConfirmation(false) 51 | .build() 52 | assertFalse(request.getUserConfirmation()) 53 | } 54 | } -------------------------------------------------------------------------------- /src/test/resources/aog_main.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "asdf", 4 | "locale": "en-US", 5 | "lastSeen": "2018-04-24T00:06:06Z", 6 | "profile": { 7 | "displayName": "FirstName LastName", 8 | "givenName": "First", 9 | "familyName": "LastName" 10 | } 11 | }, 12 | "conversation": { 13 | "conversationId": "1524851727254", 14 | "type": "NEW" 15 | }, 16 | "inputs": [ 17 | { 18 | "intent": "intent.ask.confirmation", 19 | "rawInputs": [ 20 | { 21 | "inputType": "KEYBOARD", 22 | "query": "Talk to my test app" 23 | } 24 | ] 25 | } 26 | ], 27 | "surface": { 28 | "capabilities": [ 29 | { 30 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 31 | }, 32 | { 33 | "name": "actions.capability.WEB_BROWSER" 34 | }, 35 | { 36 | "name": "actions.capability.AUDIO_OUTPUT" 37 | }, 38 | { 39 | "name": "actions.capability.SCREEN_OUTPUT" 40 | } 41 | ] 42 | }, 43 | "isInSandbox": true, 44 | "availableSurfaces": [ 45 | { 46 | "capabilities": [ 47 | { 48 | "name": "actions.capability.AUDIO_OUTPUT" 49 | }, 50 | { 51 | "name": "actions.capability.SCREEN_OUTPUT" 52 | } 53 | ] 54 | } 55 | ] 56 | } -------------------------------------------------------------------------------- /src/test/resources/aog_place_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "asdfsadf", 4 | "profile": { 5 | "displayName": "Display Name", 6 | "givenName": "Given name", 7 | "familyName": "Last name" 8 | }, 9 | "permissions": [ 10 | "NAME", 11 | "DEVICE_PRECISE_LOCATION" 12 | ], 13 | "locale": "en-US", 14 | "lastSeen": "2018-06-12T23:09:40Z" 15 | }, 16 | "conversation": { 17 | "conversationId": "1528845377613", 18 | "type": "ACTIVE", 19 | "conversationToken": "[]" 20 | }, 21 | "inputs": [ 22 | { 23 | "intent": "actions.intent.PLACE", 24 | "rawInputs": [ 25 | { 26 | "inputType": "KEYBOARD", 27 | "query": "yes" 28 | } 29 | ], 30 | "arguments": [ 31 | { 32 | "name": "PLACE", 33 | "status": { 34 | "code": 7, 35 | // Permission denied 36 | "message": "User denied location permission" 37 | } 38 | } 39 | ] 40 | } 41 | ], 42 | "surface": { 43 | "capabilities": [ 44 | { 45 | "name": "actions.capability.SCREEN_OUTPUT" 46 | }, 47 | { 48 | "name": "actions.capability.AUDIO_OUTPUT" 49 | }, 50 | { 51 | "name": "actions.capability.WEB_BROWSER" 52 | }, 53 | { 54 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 55 | } 56 | ] 57 | }, 58 | "device": { 59 | "location": { 60 | "coordinates": { 61 | "latitude": 37.4219806, 62 | "longitude": -122.0841979 63 | } 64 | } 65 | }, 66 | "isInSandbox": true, 67 | "availableSurfaces": [ 68 | { 69 | "capabilities": [ 70 | { 71 | "name": "actions.capability.SCREEN_OUTPUT" 72 | }, 73 | { 74 | "name": "actions.capability.AUDIO_OUTPUT" 75 | }, 76 | { 77 | "name": "actions.capability.WEB_BROWSER" 78 | } 79 | ] 80 | } 81 | ] 82 | } -------------------------------------------------------------------------------- /src/test/resources/aog_user_conversation_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "Abcd", 4 | "locale": "en-US", 5 | "lastSeen": "2018-05-24T19:03:47Z" 6 | }, 7 | "conversation": { 8 | "conversationId": "1234", 9 | "type": "ACTIVE", 10 | "conversationToken": "{\"data\":{\"headquarters\":[\"google1\",\"google2\"],\"history\":[],\"cats\":[],\"test\":\"hello\"}}" 11 | }, 12 | "inputs": [ 13 | { 14 | "intent": "actions.intent.CONFIRMATION", 15 | "rawInputs": [ 16 | { 17 | "inputType": "KEYBOARD", 18 | "query": "yes" 19 | } 20 | ], 21 | "arguments": [ 22 | { 23 | "name": "CONFIRMATION", 24 | "boolValue": true 25 | } 26 | ] 27 | } 28 | ], 29 | "surface": { 30 | "capabilities": [ 31 | { 32 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 33 | }, 34 | { 35 | "name": "actions.capability.WEB_BROWSER" 36 | }, 37 | { 38 | "name": "actions.capability.AUDIO_OUTPUT" 39 | }, 40 | { 41 | "name": "actions.capability.SCREEN_OUTPUT" 42 | } 43 | ] 44 | }, 45 | "isInSandbox": true, 46 | "availableSurfaces": [ 47 | { 48 | "capabilities": [ 49 | { 50 | "name": "actions.capability.WEB_BROWSER" 51 | }, 52 | { 53 | "name": "actions.capability.AUDIO_OUTPUT" 54 | }, 55 | { 56 | "name": "actions.capability.SCREEN_OUTPUT" 57 | } 58 | ] 59 | } 60 | ] 61 | } -------------------------------------------------------------------------------- /src/test/resources/aog_user_storage.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "locale": "en-US", 4 | "lastSeen": "2019-09-09T20:37:24Z", 5 | "userStorage": "{\"data\":{\"test\":\"hello\"}}", 6 | "userVerificationStatus": "VERIFIED" 7 | }, 8 | "conversation": { 9 | "conversationId": "ABwppHFh6sMQYXTHApbs8pNVdiAcvWG1UmUdO4XQEiXUYs5n3nAjQhrApN6MxiPbs6AKk3ZjuGKN-1A01nMY9mJK96zXHWpR", 10 | "type": "ACTIVE", 11 | "conversationToken": "[\"_actions_on_google\"]" 12 | }, 13 | "inputs": [ 14 | { 15 | "intent": "actions.intent.TEXT", 16 | "rawInputs": [ 17 | { 18 | "inputType": "KEYBOARD", 19 | "query": "bye" 20 | } 21 | ], 22 | "arguments": [ 23 | { 24 | "name": "text", 25 | "rawText": "bye", 26 | "textValue": "bye" 27 | } 28 | ] 29 | } 30 | ], 31 | "surface": { 32 | "capabilities": [ 33 | { 34 | "name": "actions.capability.WEB_BROWSER" 35 | }, 36 | { 37 | "name": "actions.capability.SCREEN_OUTPUT" 38 | }, 39 | { 40 | "name": "actions.capability.ACCOUNT_LINKING" 41 | }, 42 | { 43 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 44 | }, 45 | { 46 | "name": "actions.capability.AUDIO_OUTPUT" 47 | } 48 | ] 49 | }, 50 | "isInSandbox": true, 51 | "availableSurfaces": [ 52 | { 53 | "capabilities": [ 54 | { 55 | "name": "actions.capability.AUDIO_OUTPUT" 56 | }, 57 | { 58 | "name": "actions.capability.SCREEN_OUTPUT" 59 | }, 60 | { 61 | "name": "actions.capability.WEB_BROWSER" 62 | } 63 | ] 64 | } 65 | ] 66 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_all_surface_capabilities.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "asdf", 4 | "locale": "en-US", 5 | "lastSeen": "2018-04-24T00:06:06Z", 6 | "profile": { 7 | "displayName": "FirstName LastName", 8 | "givenName": "First", 9 | "familyName": "LastName" 10 | } 11 | }, 12 | "conversation": { 13 | "conversationId": "1524851727254", 14 | "type": "NEW" 15 | }, 16 | "inputs": [ 17 | { 18 | "intent": "intent.ask.confirmation", 19 | "rawInputs": [ 20 | { 21 | "inputType": "KEYBOARD", 22 | "query": "Talk to my test app" 23 | } 24 | ] 25 | } 26 | ], 27 | "surface": { 28 | "capabilities": [ 29 | { 30 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 31 | }, 32 | { 33 | "name": "actions.capability.WEB_BROWSER" 34 | }, 35 | { 36 | "name": "actions.capability.AUDIO_OUTPUT" 37 | }, 38 | { 39 | "name": "actions.capability.SCREEN_OUTPUT" 40 | }, 41 | { 42 | "name": "actions.capability.INTERACTIVE_CANVAS" 43 | } 44 | ] 45 | }, 46 | "isInSandbox": true, 47 | "availableSurfaces": [ 48 | { 49 | "capabilities": [ 50 | { 51 | "name": "actions.capability.AUDIO_OUTPUT" 52 | }, 53 | { 54 | "name": "actions.capability.SCREEN_OUTPUT" 55 | } 56 | ] 57 | } 58 | ] 59 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_argument_extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "abcde", 4 | "locale": "en-US", 5 | "userStorage": "{\"data\":{}}" 6 | }, 7 | "conversation": { 8 | "conversationId": "1234", 9 | "type": "ACTIVE", 10 | "conversationToken": "[]" 11 | }, 12 | "inputs": [ 13 | { 14 | "intent": "actions.intent.MEDIA_STATUS", 15 | "rawInputs": [ 16 | { 17 | "inputType": "VOICE" 18 | } 19 | ], 20 | "arguments": [ 21 | { 22 | "name": "MEDIA_STATUS", 23 | "extension": { 24 | "@type": "type.googleapis.com/google.actions.v2.MediaStatus", 25 | "status": "FINISHED" 26 | } 27 | } 28 | ] 29 | } 30 | ], 31 | "surface": { 32 | "capabilities": [ 33 | { 34 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 35 | }, 36 | { 37 | "name": "actions.capability.WEB_BROWSER" 38 | }, 39 | { 40 | "name": "actions.capability.SCREEN_OUTPUT" 41 | }, 42 | { 43 | "name": "actions.capability.AUDIO_OUTPUT" 44 | } 45 | ] 46 | }, 47 | "isInSandbox": true, 48 | "availableSurfaces": [ 49 | { 50 | "capabilities": [ 51 | { 52 | "name": "actions.capability.WEB_BROWSER" 53 | }, 54 | { 55 | "name": "actions.capability.SCREEN_OUTPUT" 56 | }, 57 | { 58 | "name": "actions.capability.AUDIO_OUTPUT" 59 | } 60 | ] 61 | } 62 | ], 63 | "requestType": "SIMULATOR" 64 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_arguments.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "Abcd", 4 | "locale": "fr-FR", 5 | "lastSeen": "2018-05-24T19:03:47Z" 6 | }, 7 | "conversation": { 8 | "conversationId": "1234", 9 | "type": "ACTIVE", 10 | "conversationToken": "[]" 11 | }, 12 | "inputs": [ 13 | { 14 | "intent": "actions.intent.CONFIRMATION", 15 | "rawInputs": [ 16 | { 17 | "inputType": "KEYBOARD", 18 | "query": "yes" 19 | } 20 | ], 21 | "arguments": [ 22 | { 23 | "name": "CONFIRMATION", 24 | "boolValue": true 25 | } 26 | ] 27 | } 28 | ], 29 | "surface": { 30 | "capabilities": [ 31 | { 32 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 33 | }, 34 | { 35 | "name": "actions.capability.WEB_BROWSER" 36 | }, 37 | { 38 | "name": "actions.capability.AUDIO_OUTPUT" 39 | }, 40 | { 41 | "name": "actions.capability.SCREEN_OUTPUT" 42 | } 43 | ] 44 | }, 45 | "isInSandbox": true, 46 | "availableSurfaces": [ 47 | { 48 | "capabilities": [ 49 | { 50 | "name": "actions.capability.WEB_BROWSER" 51 | }, 52 | { 53 | "name": "actions.capability.AUDIO_OUTPUT" 54 | }, 55 | { 56 | "name": "actions.capability.SCREEN_OUTPUT" 57 | } 58 | ] 59 | } 60 | ] 61 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_datetime.json: -------------------------------------------------------------------------------- 1 | { 2 | "isInSandbox": true, 3 | "surface": { 4 | "capabilities": [ 5 | { 6 | "name": "actions.capability.SCREEN_OUTPUT" 7 | }, 8 | { 9 | "name": "actions.capability.AUDIO_OUTPUT" 10 | }, 11 | { 12 | "name": "actions.capability.WEB_BROWSER" 13 | }, 14 | { 15 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 16 | } 17 | ] 18 | }, 19 | "inputs": [ 20 | { 21 | "rawInputs": [ 22 | { 23 | "query": "5pm", 24 | "inputType": "KEYBOARD" 25 | } 26 | ], 27 | "arguments": [ 28 | { 29 | "datetimeValue": { 30 | "date": { 31 | "month": 6, 32 | "year": 2018, 33 | "day": 7 34 | }, 35 | "time": { 36 | "hours": 17 37 | } 38 | }, 39 | "name": "DATETIME" 40 | } 41 | ], 42 | "intent": "actions.intent.DATETIME" 43 | } 44 | ], 45 | "user": { 46 | "lastSeen": "2018-06-06T21:10:08Z", 47 | "locale": "en-US", 48 | "userId": "123456" 49 | }, 50 | "conversation": { 51 | "conversationId": "1234", 52 | "type": "ACTIVE", 53 | "conversationToken": "[]" 54 | }, 55 | "availableSurfaces": [ 56 | { 57 | "capabilities": [ 58 | { 59 | "name": "actions.capability.SCREEN_OUTPUT" 60 | }, 61 | { 62 | "name": "actions.capability.AUDIO_OUTPUT" 63 | }, 64 | { 65 | "name": "actions.capability.WEB_BROWSER" 66 | } 67 | ] 68 | } 69 | ] 70 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_delivery_address.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "abcde", 4 | "locale": "en-US", 5 | "userStorage": "{\"data\":{}}" 6 | }, 7 | "conversation": { 8 | "conversationId": "1234", 9 | "type": "ACTIVE", 10 | "conversationToken": "[]" 11 | }, 12 | "inputs": [ 13 | { 14 | "intent": "actions.intent.DELIVERY_ADDRESS", 15 | "rawInputs": [ 16 | { 17 | "inputType": "KEYBOARD", 18 | "query": "1600 AMPHITHEATRE PKWY" 19 | } 20 | ], 21 | "arguments": [ 22 | { 23 | "name": "DELIVERY_ADDRESS_VALUE", 24 | "extension": { 25 | "@type": "type.googleapis.com/google.actions.v2.DeliveryAddressValue", 26 | "userDecision": "ACCEPTED", 27 | "location": { 28 | "coordinates": { 29 | "latitude": 37.432524, 30 | "longitude": -122.098545 31 | }, 32 | "zipCode": "94043", 33 | "city": "MOUNTAIN VIEW", 34 | "postalAddress": { 35 | "regionCode": "US", 36 | "postalCode": "94043", 37 | "administrativeArea": "CA", 38 | "locality": "MOUNTAIN VIEW", 39 | "addressLines": [ 40 | "1600 AMPHITHEATRE PKWY" 41 | ], 42 | "recipients": [ 43 | "FirstName LastName" 44 | ] 45 | }, 46 | "phoneNumber": "+0 123-456-7890" 47 | } 48 | } 49 | }, 50 | { 51 | "name": "text", 52 | "rawText": "1600 AMPHITHEATRE PKWY", 53 | "textValue": "1600 AMPHITHEATRE PKWY" 54 | } 55 | ] 56 | } 57 | ], 58 | "surface": { 59 | "capabilities": [ 60 | { 61 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 62 | }, 63 | { 64 | "name": "actions.capability.SCREEN_OUTPUT" 65 | }, 66 | { 67 | "name": "actions.capability.AUDIO_OUTPUT" 68 | }, 69 | { 70 | "name": "actions.capability.WEB_BROWSER" 71 | } 72 | ] 73 | }, 74 | "isInSandbox": true, 75 | "availableSurfaces": [ 76 | { 77 | "capabilities": [ 78 | { 79 | "name": "actions.capability.SCREEN_OUTPUT" 80 | }, 81 | { 82 | "name": "actions.capability.AUDIO_OUTPUT" 83 | }, 84 | { 85 | "name": "actions.capability.WEB_BROWSER" 86 | } 87 | ] 88 | } 89 | ], 90 | "requestType": "SIMULATOR" 91 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_final_reprompt.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "abcd", 4 | "locale": "en-US", 5 | "lastSeen": "2018-08-01T16:30:15Z" 6 | }, 7 | "conversation": { 8 | "conversationId": "1234", 9 | "type": "ACTIVE", 10 | "conversationToken": "[]" 11 | }, 12 | "inputs": [ 13 | { 14 | "intent": "actions.intent.NO_INPUT", 15 | "rawInputs": [ 16 | { 17 | "inputType": "VOICE" 18 | } 19 | ], 20 | "arguments": [ 21 | { 22 | "name": "REPROMPT_COUNT", 23 | "intValue": "2" 24 | }, 25 | { 26 | "name": "IS_FINAL_REPROMPT", 27 | "boolValue": true 28 | } 29 | ] 30 | } 31 | ], 32 | "surface": { 33 | "capabilities": [ 34 | { 35 | "name": "actions.capability.AUDIO_OUTPUT" 36 | }, 37 | { 38 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 39 | } 40 | ] 41 | }, 42 | "isInSandbox": true, 43 | "availableSurfaces": [ 44 | { 45 | "capabilities": [ 46 | { 47 | "name": "actions.capability.AUDIO_OUTPUT" 48 | }, 49 | { 50 | "name": "actions.capability.WEB_BROWSER" 51 | }, 52 | { 53 | "name": "actions.capability.SCREEN_OUTPUT" 54 | } 55 | ] 56 | } 57 | ], 58 | "requestType": "SIMULATOR" 59 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_location.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "asdfsadf", 4 | "profile": { 5 | "displayName": "Display Name", 6 | "givenName": "Given name", 7 | "familyName": "Last name" 8 | }, 9 | "permissions": [ 10 | "NAME", 11 | "DEVICE_PRECISE_LOCATION" 12 | ], 13 | "locale": "en-US", 14 | "lastSeen": "2018-06-12T23:09:40Z" 15 | }, 16 | "conversation": { 17 | "conversationId": "1528845377613", 18 | "type": "ACTIVE", 19 | "conversationToken": "[]" 20 | }, 21 | "inputs": [ 22 | { 23 | "intent": "actions.intent.PERMISSION", 24 | "rawInputs": [ 25 | { 26 | "inputType": "KEYBOARD", 27 | "query": "yes" 28 | } 29 | ], 30 | "arguments": [ 31 | { 32 | "name": "PERMISSION", 33 | "boolValue": true, 34 | "textValue": "true" 35 | } 36 | ] 37 | } 38 | ], 39 | "surface": { 40 | "capabilities": [ 41 | { 42 | "name": "actions.capability.SCREEN_OUTPUT" 43 | }, 44 | { 45 | "name": "actions.capability.AUDIO_OUTPUT" 46 | }, 47 | { 48 | "name": "actions.capability.WEB_BROWSER" 49 | }, 50 | { 51 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 52 | } 53 | ] 54 | }, 55 | "device": { 56 | "location": { 57 | "coordinates": { 58 | "latitude": 37.4219806, 59 | "longitude": -122.0841979 60 | } 61 | } 62 | }, 63 | "isInSandbox": true, 64 | "availableSurfaces": [ 65 | { 66 | "capabilities": [ 67 | { 68 | "name": "actions.capability.SCREEN_OUTPUT" 69 | }, 70 | { 71 | "name": "actions.capability.AUDIO_OUTPUT" 72 | }, 73 | { 74 | "name": "actions.capability.WEB_BROWSER" 75 | } 76 | ] 77 | } 78 | ] 79 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_option.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "abcd", 4 | "locale": "en-US", 5 | "lastSeen": "2018-08-01T16:30:15Z" 6 | }, 7 | "conversation": { 8 | "conversationId": "1234", 9 | "type": "ACTIVE", 10 | "conversationToken": "[]" 11 | }, 12 | "inputs": [ 13 | { 14 | "intent": "actions.intent.NO_INPUT", 15 | "rawInputs": [ 16 | { 17 | "inputType": "VOICE" 18 | } 19 | ], 20 | "arguments": [ 21 | { 22 | "name": "OPTION", 23 | "textValue": "2" 24 | } 25 | ] 26 | } 27 | ], 28 | "surface": { 29 | "capabilities": [ 30 | { 31 | "name": "actions.capability.AUDIO_OUTPUT" 32 | }, 33 | { 34 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 35 | } 36 | ] 37 | }, 38 | "isInSandbox": true, 39 | "availableSurfaces": [ 40 | { 41 | "capabilities": [ 42 | { 43 | "name": "actions.capability.AUDIO_OUTPUT" 44 | }, 45 | { 46 | "name": "actions.capability.WEB_BROWSER" 47 | }, 48 | { 49 | "name": "actions.capability.SCREEN_OUTPUT" 50 | } 51 | ] 52 | } 53 | ], 54 | "requestType": "SIMULATOR" 55 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_permission_denied.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "adsf", 4 | "locale": "en-US", 5 | "lastSeen": "2018-06-13T20:52:14Z" 6 | }, 7 | "conversation": { 8 | "conversationId": "1234", 9 | "type": "ACTIVE", 10 | "conversationToken": "[]" 11 | }, 12 | "inputs": [ 13 | { 14 | "intent": "actions.intent.PERMISSION", 15 | "rawInputs": [ 16 | { 17 | "inputType": "KEYBOARD", 18 | "query": "no" 19 | } 20 | ], 21 | "arguments": [ 22 | { 23 | "name": "PERMISSION", 24 | "textValue": "false" 25 | } 26 | ] 27 | } 28 | ], 29 | "surface": { 30 | "capabilities": [ 31 | { 32 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 33 | }, 34 | { 35 | "name": "actions.capability.WEB_BROWSER" 36 | }, 37 | { 38 | "name": "actions.capability.AUDIO_OUTPUT" 39 | }, 40 | { 41 | "name": "actions.capability.SCREEN_OUTPUT" 42 | } 43 | ] 44 | }, 45 | "isInSandbox": true, 46 | "availableSurfaces": [ 47 | { 48 | "capabilities": [ 49 | { 50 | "name": "actions.capability.WEB_BROWSER" 51 | }, 52 | { 53 | "name": "actions.capability.AUDIO_OUTPUT" 54 | }, 55 | { 56 | "name": "actions.capability.SCREEN_OUTPUT" 57 | } 58 | ] 59 | } 60 | ] 61 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_place.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "asdfsadf", 4 | "profile": { 5 | "displayName": "Display Name", 6 | "givenName": "Given name", 7 | "familyName": "Last name" 8 | }, 9 | "permissions": [ 10 | "NAME", 11 | "DEVICE_PRECISE_LOCATION" 12 | ], 13 | "locale": "en-US", 14 | "lastSeen": "2018-06-12T23:09:40Z" 15 | }, 16 | "conversation": { 17 | "conversationId": "1528845377613", 18 | "type": "ACTIVE", 19 | "conversationToken": "[]" 20 | }, 21 | "inputs": [ 22 | { 23 | "intent": "actions.intent.PLACE", 24 | "rawInputs": [ 25 | { 26 | "inputType": "KEYBOARD", 27 | "query": "yes" 28 | } 29 | ], 30 | "arguments": [ 31 | { 32 | "name": "PLACE", 33 | "placeValue": { 34 | "coordinates": { 35 | "latitude": 37.3911801, 36 | "longitude": -122.0810139 37 | }, 38 | "name": "Cascal", 39 | "formattedAddress": "Cascal, 400 Castro Street, Mountain View, CA 94041, United States", 40 | "placeId": "ChIJ03QfnzO3j4ARC0p7TSYoCpA" 41 | } 42 | } 43 | ] 44 | } 45 | ], 46 | "surface": { 47 | "capabilities": [ 48 | { 49 | "name": "actions.capability.SCREEN_OUTPUT" 50 | }, 51 | { 52 | "name": "actions.capability.AUDIO_OUTPUT" 53 | }, 54 | { 55 | "name": "actions.capability.WEB_BROWSER" 56 | }, 57 | { 58 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 59 | } 60 | ] 61 | }, 62 | "device": { 63 | "location": { 64 | "coordinates": { 65 | "latitude": 37.4219806, 66 | "longitude": -122.0841979 67 | } 68 | } 69 | }, 70 | "isInSandbox": true, 71 | "availableSurfaces": [ 72 | { 73 | "capabilities": [ 74 | { 75 | "name": "actions.capability.SCREEN_OUTPUT" 76 | }, 77 | { 78 | "name": "actions.capability.AUDIO_OUTPUT" 79 | }, 80 | { 81 | "name": "actions.capability.WEB_BROWSER" 82 | } 83 | ] 84 | } 85 | ] 86 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_register_update.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "abcde", 4 | "locale": "en-US", 5 | "userStorage": "{\"data\":{}}" 6 | }, 7 | "conversation": { 8 | "conversationId": "1234", 9 | "type": "ACTIVE", 10 | "conversationToken": "[]" 11 | }, 12 | "inputs": [ 13 | { 14 | "intent": "actions.intent.REGISTER_UPDATE", 15 | "rawInputs": [ 16 | { 17 | "inputType": "TOUCH", 18 | "query": "1pm" 19 | } 20 | ], 21 | "arguments": [ 22 | { 23 | "name": "REGISTER_UPDATE", 24 | "extension": { 25 | "@type": "type.googleapis.com/google.actions.v2.RegisterUpdateValue", 26 | "status": "OK" 27 | } 28 | } 29 | ] 30 | } 31 | ], 32 | "surface": { 33 | "capabilities": [ 34 | { 35 | "name": "actions.capability.SCREEN_OUTPUT" 36 | }, 37 | { 38 | "name": "actions.capability.AUDIO_OUTPUT" 39 | }, 40 | { 41 | "name": "actions.capability.WEB_BROWSER" 42 | }, 43 | { 44 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 45 | } 46 | ] 47 | }, 48 | "isInSandbox": true, 49 | "availableSurfaces": [ 50 | { 51 | "capabilities": [ 52 | { 53 | "name": "actions.capability.SCREEN_OUTPUT" 54 | }, 55 | { 56 | "name": "actions.capability.AUDIO_OUTPUT" 57 | }, 58 | { 59 | "name": "actions.capability.WEB_BROWSER" 60 | } 61 | ] 62 | } 63 | ], 64 | "requestType": "SIMULATOR" 65 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_reprompt.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "abcd", 4 | "locale": "en-US", 5 | "lastSeen": "2018-08-01T16:30:15Z" 6 | }, 7 | "conversation": { 8 | "conversationId": "1234", 9 | "type": "ACTIVE", 10 | "conversationToken": "[]" 11 | }, 12 | "inputs": [ 13 | { 14 | "intent": "actions.intent.NO_INPUT", 15 | "rawInputs": [ 16 | { 17 | "inputType": "VOICE" 18 | } 19 | ], 20 | "arguments": [ 21 | { 22 | "name": "REPROMPT_COUNT", 23 | "intValue": "1" 24 | }, 25 | { 26 | "name": "IS_FINAL_REPROMPT", 27 | "boolValue": false 28 | } 29 | ] 30 | } 31 | ], 32 | "surface": { 33 | "capabilities": [ 34 | { 35 | "name": "actions.capability.AUDIO_OUTPUT" 36 | }, 37 | { 38 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 39 | } 40 | ] 41 | }, 42 | "isInSandbox": true, 43 | "availableSurfaces": [ 44 | { 45 | "capabilities": [ 46 | { 47 | "name": "actions.capability.AUDIO_OUTPUT" 48 | }, 49 | { 50 | "name": "actions.capability.WEB_BROWSER" 51 | }, 52 | { 53 | "name": "actions.capability.SCREEN_OUTPUT" 54 | } 55 | ] 56 | } 57 | ], 58 | "requestType": "SIMULATOR" 59 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_transaction_decision.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "abcde", 4 | "locale": "en-US", 5 | "userStorage": "{\"data\":{}}" 6 | }, 7 | "conversation": { 8 | "conversationId": "1234", 9 | "type": "ACTIVE", 10 | "conversationToken": "[]" 11 | }, 12 | "inputs": [ 13 | { 14 | "intent": "actions.intent.TRANSACTION_DECISION", 15 | "rawInputs": [ 16 | { 17 | "inputType": "KEYBOARD" 18 | } 19 | ], 20 | "arguments": [ 21 | { 22 | "name": "TRANSACTION_DECISION_VALUE", 23 | "extension": { 24 | "@type": "type.googleapis.com/google.actions.v2.TransactionDecisionValue", 25 | "userDecision": "ORDER_ACCEPTED", 26 | "order": { 27 | "finalOrder": { 28 | "cart": { 29 | "merchant": { 30 | "id": "merchant_id", 31 | "name": "merchant_name" 32 | }, 33 | "otherItems": [ 34 | ], 35 | "lineItems": [ 36 | { 37 | "name": "item_name", 38 | "type": "REGULAR", 39 | "id": "item_id", 40 | "quantity": 1, 41 | "price": { 42 | "type": "ACTUAL", 43 | "amount": { 44 | "currencyCode": "USD", 45 | "units": "1", 46 | "nanos": 990000000 47 | } 48 | }, 49 | "subLines": [ 50 | { 51 | "note": "Note" 52 | } 53 | ] 54 | } 55 | ] 56 | }, 57 | "totalPrice": { 58 | "type": "ESTIMATE", 59 | "amount": { 60 | "currencyCode": "USD", 61 | "units": "1", 62 | "nanos": 990000000 63 | } 64 | }, 65 | "id": "", 66 | "extension": { 67 | "@type": "type.googleapis.com/google.actions.v2.orders.GenericExtension", 68 | "locations": [ 69 | { 70 | "type": "DELIVERY", 71 | "location": { 72 | "postalAddress": { 73 | "regionCode": "US", 74 | "postalCode": "94043", 75 | "administrativeArea": "CA", 76 | "locality": "MOUNTAIN VIEW", 77 | "addressLines": [ 78 | "1600 AMPHITHEATRE PKWY" 79 | ], 80 | "recipients": [ 81 | "FirstName LastName" 82 | ] 83 | } 84 | } 85 | } 86 | ] 87 | } 88 | }, 89 | "googleOrderId": "1234", 90 | "orderDate": "2019-01-11T00:14:39.661Z", 91 | "paymentInfo": { 92 | "displayName": "VISA-1234", 93 | "paymentType": "PAYMENT_CARD" 94 | } 95 | }, 96 | "checkResult": { 97 | "resultType": "OK" 98 | } 99 | } 100 | }, 101 | { 102 | "name": "text" 103 | } 104 | ] 105 | } 106 | ], 107 | "surface": { 108 | "capabilities": [ 109 | { 110 | "name": "actions.capability.WEB_BROWSER" 111 | }, 112 | { 113 | "name": "actions.capability.AUDIO_OUTPUT" 114 | }, 115 | { 116 | "name": "actions.capability.SCREEN_OUTPUT" 117 | }, 118 | { 119 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 120 | } 121 | ] 122 | }, 123 | "isInSandbox": true, 124 | "availableSurfaces": [ 125 | { 126 | "capabilities": [ 127 | { 128 | "name": "actions.capability.WEB_BROWSER" 129 | }, 130 | { 131 | "name": "actions.capability.AUDIO_OUTPUT" 132 | }, 133 | { 134 | "name": "actions.capability.SCREEN_OUTPUT" 135 | } 136 | ] 137 | } 138 | ], 139 | "requestType": "SIMULATOR" 140 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_transaction_requirements_check.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "abcde", 4 | "locale": "en-US", 5 | "userStorage": "{\"data\":{}}" 6 | }, 7 | "conversation": { 8 | "conversationId": "1234", 9 | "type": "ACTIVE", 10 | "conversationToken": "[]" 11 | }, 12 | "inputs": [ 13 | { 14 | "intent": "actions.intent.TRANSACTION_REQUIREMENTS_CHECK", 15 | "rawInputs": [ 16 | { 17 | "inputType": "TOUCH" 18 | } 19 | ], 20 | "arguments": [ 21 | { 22 | "name": "TRANSACTION_REQUIREMENTS_CHECK_RESULT", 23 | "extension": { 24 | "@type": "type.googleapis.com/google.actions.v2.TransactionRequirementsCheckResult", 25 | "resultType": "OK" 26 | } 27 | }, 28 | { 29 | "name": "text" 30 | } 31 | ] 32 | } 33 | ], 34 | "surface": { 35 | "capabilities": [ 36 | { 37 | "name": "actions.capability.WEB_BROWSER" 38 | }, 39 | { 40 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 41 | }, 42 | { 43 | "name": "actions.capability.SCREEN_OUTPUT" 44 | }, 45 | { 46 | "name": "actions.capability.AUDIO_OUTPUT" 47 | } 48 | ] 49 | }, 50 | "isInSandbox": true, 51 | "availableSurfaces": [ 52 | { 53 | "capabilities": [ 54 | { 55 | "name": "actions.capability.WEB_BROWSER" 56 | }, 57 | { 58 | "name": "actions.capability.SCREEN_OUTPUT" 59 | }, 60 | { 61 | "name": "actions.capability.AUDIO_OUTPUT" 62 | } 63 | ] 64 | } 65 | ], 66 | "requestType": "SIMULATOR" 67 | } -------------------------------------------------------------------------------- /src/test/resources/aog_with_update_permission.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": { 3 | "userId": "123456", 4 | "permissions": [ 5 | "UPDATE" 6 | ], 7 | "locale": "en-US" 8 | }, 9 | "conversation": { 10 | "conversationId": "1234", 11 | "type": "ACTIVE", 12 | "conversationToken": "[]" 13 | }, 14 | "inputs": [ 15 | { 16 | "intent": "actions.intent.PERMISSION", 17 | "rawInputs": [ 18 | { 19 | "inputType": "KEYBOARD", 20 | "query": "yes" 21 | } 22 | ], 23 | "arguments": [ 24 | { 25 | "name": "PERMISSION", 26 | "boolValue": true, 27 | "textValue": "true" 28 | }, 29 | { 30 | "name": "UPDATES_USER_ID", 31 | "textValue": "123456" 32 | } 33 | ] 34 | } 35 | ], 36 | "surface": { 37 | "capabilities": [ 38 | { 39 | "name": "actions.capability.AUDIO_OUTPUT" 40 | }, 41 | { 42 | "name": "actions.capability.SCREEN_OUTPUT" 43 | }, 44 | { 45 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 46 | }, 47 | { 48 | "name": "actions.capability.WEB_BROWSER" 49 | } 50 | ] 51 | }, 52 | "isInSandbox": true, 53 | "availableSurfaces": [ 54 | { 55 | "capabilities": [ 56 | { 57 | "name": "actions.capability.AUDIO_OUTPUT" 58 | }, 59 | { 60 | "name": "actions.capability.SCREEN_OUTPUT" 61 | }, 62 | { 63 | "name": "actions.capability.WEB_BROWSER" 64 | } 65 | ] 66 | } 67 | ], 68 | "requestType": "SIMULATOR" 69 | } -------------------------------------------------------------------------------- /src/test/resources/dialogflow_complete.json: -------------------------------------------------------------------------------- 1 | { 2 | "responseId": "acdddd", 3 | "queryResult": { 4 | "queryText": "blue grey coffee", 5 | "parameters": { 6 | "fakeColor": "blue grey coffee" 7 | }, 8 | "allRequiredParamsPresent": true, 9 | "fulfillmentMessages": [ 10 | { 11 | "text": { 12 | "text": [ 13 | "" 14 | ] 15 | } 16 | } 17 | ], 18 | "outputContexts": [ 19 | { 20 | "name": "projects/projectId/agent/sessions/sessionId/contexts/favoritecolorintent-followup", 21 | "parameters": { 22 | "fakeColor": "blue grey coffee", 23 | "color": "yellow", 24 | "fakeColor.original": "blue grey coffee", 25 | "color.original": "Yellow" 26 | } 27 | }, 28 | { 29 | "name": "projects/projectId/agent/sessions/sessionId/contexts/actions_capability_screen_output", 30 | "parameters": { 31 | "fakeColor": "blue grey coffee", 32 | "fakeColor.original": "blue grey coffee" 33 | } 34 | }, 35 | { 36 | "name": "projects/projectId/agent/sessions/sessionId/contexts/_actions_on_google", 37 | "lifespanCount": 97, 38 | "parameters": { 39 | "fakeColor": "blue grey coffee", 40 | "color": "yellow", 41 | "data": "{\"userName\":\"first last\"}", 42 | "fakeColor.original": "blue grey coffee", 43 | "color.original": "Yellow" 44 | } 45 | }, 46 | { 47 | "name": "projects/projectId/agent/sessions/sessionId/contexts/actions_capability_audio_output", 48 | "parameters": { 49 | "fakeColor": "blue grey coffee", 50 | "fakeColor.original": "blue grey coffee" 51 | } 52 | }, 53 | { 54 | "name": "projects/projectId/agent/sessions/sessionId/contexts/google_assistant_input_type_keyboard", 55 | "parameters": { 56 | "fakeColor": "blue grey coffee", 57 | "fakeColor.original": "blue grey coffee" 58 | } 59 | }, 60 | { 61 | "name": "projects/projectId/agent/sessions/sessionId/contexts/actions_capability_web_browser", 62 | "parameters": { 63 | "fakeColor": "blue grey coffee", 64 | "fakeColor.original": "blue grey coffee" 65 | } 66 | }, 67 | { 68 | "name": "projects/projectId/agent/sessions/sessionId/contexts/actions_capability_media_response_audio", 69 | "parameters": { 70 | "fakeColor": "blue grey coffee", 71 | "fakeColor.original": "blue grey coffee" 72 | } 73 | } 74 | ], 75 | "intent": { 76 | "name": "projects/projectId/agent/intents/efded27e-5d6b-437e-bdb4-09a29d1fcaee", 77 | "displayName": "favorite fake color" 78 | }, 79 | "intentDetectionConfidence": 1, 80 | "diagnosticInfo": {}, 81 | "languageCode": "en-us" 82 | }, 83 | "originalDetectIntentRequest": { 84 | "source": "google", 85 | "version": "2", 86 | "payload": { 87 | "isInSandbox": true, 88 | "surface": { 89 | "capabilities": [ 90 | { 91 | "name": "actions.capability.WEB_BROWSER" 92 | }, 93 | { 94 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 95 | }, 96 | { 97 | "name": "actions.capability.SCREEN_OUTPUT" 98 | }, 99 | { 100 | "name": "actions.capability.AUDIO_OUTPUT" 101 | } 102 | ] 103 | }, 104 | "inputs": [ 105 | { 106 | "rawInputs": [ 107 | { 108 | "query": "blue grey coffee", 109 | "inputType": "KEYBOARD" 110 | } 111 | ], 112 | "arguments": [ 113 | { 114 | "rawText": "blue grey coffee", 115 | "textValue": "blue grey coffee", 116 | "name": "text" 117 | } 118 | ], 119 | "intent": "actions.intent.TEXT" 120 | } 121 | ], 122 | "user": { 123 | "userStorage": "{\"data\":{}}", 124 | "lastSeen": "2018-05-06T02:23:10Z", 125 | "permissions": [ 126 | "NAME" 127 | ], 128 | "profile": { 129 | "displayName": "Display name", 130 | "givenName": "Given name", 131 | "familyName": "Family name" 132 | }, 133 | "locale": "en-US", 134 | "userId": "adsf" 135 | }, 136 | "conversation": { 137 | "conversationId": "1234", 138 | "type": "ACTIVE", 139 | "conversationToken": "[\"_actions_on_google\",\"favoritecolorintent-followup\"]" 140 | }, 141 | "availableSurfaces": [ 142 | { 143 | "capabilities": [ 144 | { 145 | "name": "actions.capability.SCREEN_OUTPUT" 146 | }, 147 | { 148 | "name": "actions.capability.AUDIO_OUTPUT" 149 | } 150 | ] 151 | } 152 | ] 153 | } 154 | }, 155 | "session": "projects/projectId/agent/sessions/sessionId" 156 | } -------------------------------------------------------------------------------- /src/test/resources/dialogflow_package_entitlements.json: -------------------------------------------------------------------------------- 1 | { 2 | "responseId": "d12a1036-2cfd-4e8d-ba98-c2031ad23f64", 3 | "queryResult": { 4 | "queryText": "actions_intent_COMPLETE_PURCHASE", 5 | "parameters": {}, 6 | "allRequiredParamsPresent": true, 7 | "fulfillmentText": "", 8 | "fulfillmentMessages": [ 9 | { 10 | "text": { 11 | "text": [ 12 | "" 13 | ] 14 | } 15 | } 16 | ], 17 | "outputContexts": [ 18 | { 19 | "name": "projects/projectId/agent/sessions/sessionId/contexts/actions_intent_complete_purchase", 20 | "parameters": { 21 | "text": "", 22 | "COMPLETE_PURCHASE_VALUE": { 23 | "@type": "type.googleapis.com/google.actions.transactions.v3.CompletePurchaseValue", 24 | "purchaseStatus": "PURCHASE_STATUS_ALREADY_OWNED" 25 | } 26 | } 27 | }, 28 | { 29 | "name": "projects/projectId/agent/sessions/sessionId/contexts/actions_capability_screen_output" 30 | }, 31 | { 32 | "name": "projects/projectId/agent/sessions/sessionId/contexts/build-the-order", 33 | "lifespanCount": 3, 34 | "parameters": { 35 | "SKU.original": "", 36 | "SKU": "" 37 | } 38 | }, 39 | { 40 | "name": "projects/projectId/agent/sessions/sessionId/contexts/_actions_on_google", 41 | "lifespanCount": 98, 42 | "parameters": { 43 | "data": "{\"skuDetailsList\":\"{\\\"gold_monthly\\\":{\\\"title\\\":\\\"Title\\\",\\\"description\\\":\\\"Description \\\",\\\"skuId\\\":{\\\"id\\\":\\\"gold_monthly\\\",\\\"packageName\\\":\\\"package.name\\\",\\\"skuType\\\":\\\"SKU_TYPE_SUBSCRIPTION\\\"},\\\"formattedPrice\\\":\\\"$8.99\\\"},\\\"premium\\\":{\\\"title\\\":\\\"Title\\\",\\\"description\\\":\\\"Description!\\\",\\\"skuId\\\":{\\\"id\\\":\\\"premium\\\",\\\"packageName\\\":\\\"package.name\\\",\\\"skuType\\\":\\\"SKU_TYPE_IN_APP\\\"},\\\"formattedPrice\\\":\\\"$0.99\\\"},\\\"gas\\\":{\\\"title\\\":\\\"Title\\\",\\\"description\\\":\\\"Description\\\",\\\"skuId\\\":{\\\"id\\\":\\\"gas\\\",\\\"packageName\\\":\\\"package.name\\\",\\\"skuType\\\":\\\"SKU_TYPE_IN_APP\\\"},\\\"formattedPrice\\\":\\\"$1.99\\\"},\\\"gold_yearly\\\":{\\\"title\\\":\\\"Title\\\",\\\"description\\\":\\\"Description\\\",\\\"skuId\\\":{\\\"id\\\":\\\"gold_yearly\\\",\\\"packageName\\\":\\\"package.name\\\",\\\"skuType\\\":\\\"SKU_TYPE_SUBSCRIPTION\\\"},\\\"formattedPrice\\\":\\\"$89.99\\\"}}\",\"purchasedItemSku\":{\"title\":\"Title\",\"description\":\"Description\",\"skuId\":{\"id\":\"gas\",\"packageName\":\"package.name\",\"skuType\":\"SKU_TYPE_IN_APP\"},\"formattedPrice\":\"$1.99\"}}", 44 | "SKU.original": "", 45 | "SKU": "" 46 | } 47 | }, 48 | { 49 | "name": "projects/projectId/agent/sessions/sessionId/contexts/actions_capability_audio_output" 50 | }, 51 | { 52 | "name": "projects/projectId/agent/sessions/sessionId/contexts/actions_capability_account_linking" 53 | }, 54 | { 55 | "name": "projects/projectId/agent/sessions/sessionId/contexts/google_assistant_input_type_keyboard" 56 | }, 57 | { 58 | "name": "projects/projectId/agent/sessions/sessionId/contexts/actions_capability_web_browser" 59 | }, 60 | { 61 | "name": "projects/projectId/agent/sessions/sessionId/contexts/actions_capability_media_response_audio" 62 | } 63 | ], 64 | "intent": { 65 | "name": "projects/projectId/agent/intents/id", 66 | "displayName": "Describe the Purchase Status" 67 | }, 68 | "intentDetectionConfidence": 1, 69 | "languageCode": "en-us" 70 | }, 71 | "originalDetectIntentRequest": { 72 | "source": "google", 73 | "version": "2", 74 | "payload": { 75 | "isInSandbox": true, 76 | "surface": { 77 | "capabilities": [ 78 | { 79 | "name": "actions.capability.ACCOUNT_LINKING" 80 | }, 81 | { 82 | "name": "actions.capability.WEB_BROWSER" 83 | }, 84 | { 85 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 86 | }, 87 | { 88 | "name": "actions.capability.AUDIO_OUTPUT" 89 | }, 90 | { 91 | "name": "actions.capability.SCREEN_OUTPUT" 92 | } 93 | ] 94 | }, 95 | "requestType": "SIMULATOR", 96 | "inputs": [ 97 | { 98 | "rawInputs": [ 99 | { 100 | "inputType": "KEYBOARD" 101 | } 102 | ], 103 | "arguments": [ 104 | { 105 | "extension": { 106 | "@type": "type.googleapis.com/google.actions.transactions.v3.CompletePurchaseValue", 107 | "purchaseStatus": "PURCHASE_STATUS_ALREADY_OWNED" 108 | }, 109 | "name": "COMPLETE_PURCHASE_VALUE" 110 | }, 111 | { 112 | "name": "text" 113 | } 114 | ], 115 | "intent": "actions.intent.COMPLETE_PURCHASE" 116 | } 117 | ], 118 | "user": { 119 | "packageEntitlements": [ 120 | { 121 | "entitlements": [ 122 | { 123 | "inAppDetails": { 124 | "inAppPurchaseData": { 125 | "purchaseToken": "purchaseToken", 126 | "productId": "productId", 127 | "orderId": "orderId", 128 | "purchaseTime": 1557772151801, 129 | "packageName": "package.name", 130 | "purchaseState": 0 131 | }, 132 | "inAppDataSignature": "signature" 133 | }, 134 | "sku": "sku", 135 | "skuType": "IN_APP" 136 | }, 137 | { 138 | "inAppDetails": { 139 | "inAppPurchaseData": { 140 | "purchaseToken": "purchaseToken", 141 | "productId": "productId", 142 | "orderId": "orderId", 143 | "purchaseTime": 1557772151801, 144 | "packageName": "package.name", 145 | "purchaseState": 0 146 | }, 147 | "inAppDataSignature": "signature" 148 | }, 149 | "sku": "sku", 150 | "skuType": "IN_APP" 151 | } 152 | ], 153 | "packageName": "package.name" 154 | } 155 | ], 156 | "userStorage": "{\"data\":{}}", 157 | "lastSeen": "2019-05-13T22:19:56Z", 158 | "locale": "en-US", 159 | "userId": "userId" 160 | }, 161 | "conversation": { 162 | "conversationId": "conversationId", 163 | "type": "ACTIVE", 164 | "conversationToken": "[\"_actions_on_google\",\"build-the-order\"]" 165 | }, 166 | "availableSurfaces": [ 167 | { 168 | "capabilities": [ 169 | { 170 | "name": "actions.capability.AUDIO_OUTPUT" 171 | }, 172 | { 173 | "name": "actions.capability.SCREEN_OUTPUT" 174 | }, 175 | { 176 | "name": "actions.capability.WEB_BROWSER" 177 | } 178 | ] 179 | } 180 | ] 181 | } 182 | }, 183 | "session": "projects/projectId/agent/sessions/id" 184 | } -------------------------------------------------------------------------------- /src/test/resources/dialogflow_user_storage.json: -------------------------------------------------------------------------------- 1 | { 2 | "responseId": "95012a31-e61c-41c8-ad2b-fb6a042d47d2-763f7f5c", 3 | "queryResult": { 4 | "queryText": "bye", 5 | "parameters": {}, 6 | "allRequiredParamsPresent": true, 7 | "fulfillmentMessages": [ 8 | { 9 | "text": { 10 | "text": [ 11 | "" 12 | ] 13 | } 14 | } 15 | ], 16 | "outputContexts": [ 17 | { 18 | "name": "projects/PROJECT_ID/agent/sessions/ABwppHFh6sMQYXTHApbs8pNVdiAcvWG1UmUdO4XQEiXUYs5n3nAjQhrApN6MxiPbs6AKk3ZjuGKN-1A01nMY9mJK96zXHWpR/contexts/actions_capability_web_browser" 19 | }, 20 | { 21 | "name": "projects/PROJECT_ID/agent/sessions/ABwppHFh6sMQYXTHApbs8pNVdiAcvWG1UmUdO4XQEiXUYs5n3nAjQhrApN6MxiPbs6AKk3ZjuGKN-1A01nMY9mJK96zXHWpR/contexts/actions_capability_screen_output" 22 | }, 23 | { 24 | "name": "projects/PROJECT_ID/agent/sessions/ABwppHFh6sMQYXTHApbs8pNVdiAcvWG1UmUdO4XQEiXUYs5n3nAjQhrApN6MxiPbs6AKk3ZjuGKN-1A01nMY9mJK96zXHWpR/contexts/actions_capability_account_linking" 25 | }, 26 | { 27 | "name": "projects/PROJECT_ID/agent/sessions/ABwppHFh6sMQYXTHApbs8pNVdiAcvWG1UmUdO4XQEiXUYs5n3nAjQhrApN6MxiPbs6AKk3ZjuGKN-1A01nMY9mJK96zXHWpR/contexts/actions_capability_media_response_audio" 28 | }, 29 | { 30 | "name": "projects/PROJECT_ID/agent/sessions/ABwppHFh6sMQYXTHApbs8pNVdiAcvWG1UmUdO4XQEiXUYs5n3nAjQhrApN6MxiPbs6AKk3ZjuGKN-1A01nMY9mJK96zXHWpR/contexts/actions_capability_audio_output" 31 | }, 32 | { 33 | "name": "projects/PROJECT_ID/agent/sessions/ABwppHFh6sMQYXTHApbs8pNVdiAcvWG1UmUdO4XQEiXUYs5n3nAjQhrApN6MxiPbs6AKk3ZjuGKN-1A01nMY9mJK96zXHWpR/contexts/google_assistant_input_type_keyboard" 34 | }, 35 | { 36 | "name": "projects/PROJECT_ID/agent/sessions/ABwppHFh6sMQYXTHApbs8pNVdiAcvWG1UmUdO4XQEiXUYs5n3nAjQhrApN6MxiPbs6AKk3ZjuGKN-1A01nMY9mJK96zXHWpR/contexts/_actions_on_google", 37 | "lifespanCount": 98, 38 | "parameters": { 39 | "data": "{}" 40 | } 41 | } 42 | ], 43 | "intent": { 44 | "name": "projects/test-kotlin-user-storage/agent/intents/0230f1ce-724f-4a23-8cb3-05cbb503692d", 45 | "displayName": "bye" 46 | }, 47 | "intentDetectionConfidence": 1, 48 | "languageCode": "en" 49 | }, 50 | "originalDetectIntentRequest": { 51 | "source": "google", 52 | "version": "2", 53 | "payload": { 54 | "user": { 55 | "locale": "en-US", 56 | "lastSeen": "2019-09-09T20:37:24Z", 57 | "userStorage": "{\"data\":{\"test\":\"hello\"}}", 58 | "userVerificationStatus": "VERIFIED" 59 | }, 60 | "conversation": { 61 | "conversationId": "ABwppHFh6sMQYXTHApbs8pNVdiAcvWG1UmUdO4XQEiXUYs5n3nAjQhrApN6MxiPbs6AKk3ZjuGKN-1A01nMY9mJK96zXHWpR", 62 | "type": "ACTIVE", 63 | "conversationToken": "[\"_actions_on_google\"]" 64 | }, 65 | "inputs": [ 66 | { 67 | "intent": "actions.intent.TEXT", 68 | "rawInputs": [ 69 | { 70 | "inputType": "KEYBOARD", 71 | "query": "bye" 72 | } 73 | ], 74 | "arguments": [ 75 | { 76 | "name": "text", 77 | "rawText": "bye", 78 | "textValue": "bye" 79 | } 80 | ] 81 | } 82 | ], 83 | "surface": { 84 | "capabilities": [ 85 | { 86 | "name": "actions.capability.WEB_BROWSER" 87 | }, 88 | { 89 | "name": "actions.capability.SCREEN_OUTPUT" 90 | }, 91 | { 92 | "name": "actions.capability.ACCOUNT_LINKING" 93 | }, 94 | { 95 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 96 | }, 97 | { 98 | "name": "actions.capability.AUDIO_OUTPUT" 99 | } 100 | ] 101 | }, 102 | "isInSandbox": true, 103 | "availableSurfaces": [ 104 | { 105 | "capabilities": [ 106 | { 107 | "name": "actions.capability.AUDIO_OUTPUT" 108 | }, 109 | { 110 | "name": "actions.capability.SCREEN_OUTPUT" 111 | }, 112 | { 113 | "name": "actions.capability.WEB_BROWSER" 114 | } 115 | ] 116 | } 117 | ], 118 | "requestType": "SIMULATOR" 119 | } 120 | }, 121 | "session": "projects/PROJECT_ID/agent/sessions/ABwppHFh6sMQYXTHApbs8pNVdiAcvWG1UmUdO4XQEiXUYs5n3nAjQhrApN6MxiPbs6AKk3ZjuGKN-1A01nMY9mJK96zXHWpR" 122 | } -------------------------------------------------------------------------------- /src/test/resources/dialogflow_welcome.json: -------------------------------------------------------------------------------- 1 | { 2 | "responseId": "d054eb88-f8bc-4d7b-a5c6-7462effd0f50", 3 | "session": "test", 4 | "queryResult": { 5 | "queryText": "hello", 6 | "action": "input.welcome", 7 | "parameters": {}, 8 | "allRequiredParamsPresent": true, 9 | "fulfillmentText": "Welcome! What is your favorite color?", 10 | "fulfillmentMessages": [ 11 | { 12 | "text": { 13 | "text": [ 14 | "Welcome! What is your favorite color?" 15 | ] 16 | } 17 | } 18 | ], 19 | "intent": { 20 | "name": "projects/ksub-actions-codelab-1/agent/intents/56c03a46-c36e-450c-84c7-5ca22756ac9f", 21 | "displayName": "Default Welcome Intent" 22 | }, 23 | "intentDetectionConfidence": 1, 24 | "diagnosticInfo": {}, 25 | "languageCode": "en" 26 | }, 27 | "originalDetectIntentRequest": { 28 | "source": "google", 29 | "version": "2", 30 | "payload": {} 31 | } 32 | } -------------------------------------------------------------------------------- /src/test/resources/dialogflow_with_conv_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "responseId": "responseId", 3 | "queryResult": { 4 | "queryText": "sure", 5 | "action": "tell.fact", 6 | "parameters": { 7 | "category": "history" 8 | }, 9 | "allRequiredParamsPresent": true, 10 | "fulfillmentMessages": [ 11 | { 12 | "text": { 13 | "text": [ 14 | "" 15 | ] 16 | } 17 | } 18 | ], 19 | "outputContexts": [ 20 | { 21 | "name": "projects/projectId/agent/sessions/sessionId/contexts/choose_fact-followup", 22 | "lifespanCount": 5, 23 | "parameters": { 24 | "category.original": "", 25 | "category": "history" 26 | } 27 | }, 28 | { 29 | "name": "projects/projectId/agent/sessions/sessionId/contexts/_actions_on_google", 30 | "lifespanCount": 98, 31 | "parameters": { 32 | "data": "{\"headquarters\":[\"google_headquarters_fact_1\",\"google_headquarters_fact_2\",\"google_headquarters_fact_3\"],\"cats\":[\"cat_fact_1\",\"cat_fact_2\",\"cat_fact_3\"],\"history\":[],\"test\":\"hello\"}", 33 | "category.original": "", 34 | "category": "history" 35 | } 36 | } 37 | ], 38 | "intent": { 39 | "name": "projects/projectId/agent/intents/intentid", 40 | "displayName": "tell_fact" 41 | }, 42 | "intentDetectionConfidence": 1, 43 | "languageCode": "en" 44 | }, 45 | "originalDetectIntentRequest": { 46 | "payload": {} 47 | }, 48 | "session": "projects/projectId/agent/sessions/sessionId" 49 | } -------------------------------------------------------------------------------- /src/test/resources/dialogflow_with_datetime_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "responseId": "4", 3 | "queryResult": { 4 | "queryText": "actions_intent_DATETIME", 5 | "parameters": {}, 6 | "allRequiredParamsPresent": true, 7 | "fulfillmentMessages": [ 8 | { 9 | "text": { 10 | "text": [ 11 | "" 12 | ] 13 | } 14 | } 15 | ], 16 | "outputContexts": [ 17 | { 18 | "name": "projects/project-nameagent/sessions/1234/contexts/actions_capability_screen_output" 19 | }, 20 | { 21 | "name": "projects/project-nameagent/sessions/1234/contexts/actions_intent_datetime", 22 | "parameters": { 23 | "DATETIME": { 24 | "date": { 25 | "month": 6, 26 | "year": 2018, 27 | "day": 7 28 | }, 29 | "time": { 30 | "hours": 17 31 | } 32 | } 33 | } 34 | }, 35 | { 36 | "name": "projects/project-nameagent/sessions/1234/contexts/actions_capability_audio_output" 37 | }, 38 | { 39 | "name": "projects/project-nameagent/sessions/1234/contexts/google_assistant_input_type_keyboard" 40 | }, 41 | { 42 | "name": "projects/project-nameagent/sessions/1234/contexts/actions_capability_media_response_audio" 43 | }, 44 | { 45 | "name": "projects/project-nameagent/sessions/1234/contexts/actions_capability_web_browser" 46 | } 47 | ], 48 | "intent": { 49 | "name": "projects/project-nameagent/intents/0327af89-7be3-4f72-9e85-a6558c664245", 50 | "displayName": "actions_intent_datetime" 51 | }, 52 | "intentDetectionConfidence": 1, 53 | "diagnosticInfo": {}, 54 | "languageCode": "en-us" 55 | }, 56 | "originalDetectIntentRequest": { 57 | "source": "google", 58 | "version": "2", 59 | "payload": { 60 | "isInSandbox": true, 61 | "surface": { 62 | "capabilities": [ 63 | { 64 | "name": "actions.capability.SCREEN_OUTPUT" 65 | }, 66 | { 67 | "name": "actions.capability.AUDIO_OUTPUT" 68 | }, 69 | { 70 | "name": "actions.capability.WEB_BROWSER" 71 | }, 72 | { 73 | "name": "actions.capability.MEDIA_RESPONSE_AUDIO" 74 | } 75 | ] 76 | }, 77 | "inputs": [ 78 | { 79 | "rawInputs": [ 80 | { 81 | "query": "5pm", 82 | "inputType": "KEYBOARD" 83 | } 84 | ], 85 | "arguments": [ 86 | { 87 | "datetimeValue": { 88 | "date": { 89 | "month": 6, 90 | "year": 2018, 91 | "day": 7 92 | }, 93 | "time": { 94 | "hours": 17 95 | } 96 | }, 97 | "name": "DATETIME" 98 | } 99 | ], 100 | "intent": "actions.intent.DATETIME" 101 | } 102 | ], 103 | "user": { 104 | "lastSeen": "2018-06-06T21:10:08Z", 105 | "locale": "en-US", 106 | "userId": "123456" 107 | }, 108 | "conversation": { 109 | "conversationId": "1234", 110 | "type": "ACTIVE", 111 | "conversationToken": "[]" 112 | }, 113 | "availableSurfaces": [ 114 | { 115 | "capabilities": [ 116 | { 117 | "name": "actions.capability.SCREEN_OUTPUT" 118 | }, 119 | { 120 | "name": "actions.capability.AUDIO_OUTPUT" 121 | }, 122 | { 123 | "name": "actions.capability.WEB_BROWSER" 124 | } 125 | ] 126 | } 127 | ] 128 | } 129 | }, 130 | "session": "projects/project-nameagent/sessions/1234" 131 | } 132 | -------------------------------------------------------------------------------- /src/test/resources/input.json: -------------------------------------------------------------------------------- 1 | { 2 | "rawInputs": [ 3 | { 4 | "query": "5pm", 5 | "inputType": "KEYBOARD" 6 | } 7 | ], 8 | "arguments": [ 9 | { 10 | "datetimeValue": { 11 | "date": { 12 | "month": 6, 13 | "year": 2018, 14 | "day": 7 15 | }, 16 | "time": { 17 | "hours": 17, 18 | "minutes": 0, 19 | "seconds": 0 20 | } 21 | }, 22 | "name": "DATETIME" 23 | } 24 | ], 25 | "intent": "actions.intent.DATETIME" 26 | } 27 | -------------------------------------------------------------------------------- /src/test/resources/smarthome_disconnect_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf", 3 | "inputs": [{ 4 | "intent": "action.devices.DISCONNECT" 5 | }] 6 | } -------------------------------------------------------------------------------- /src/test/resources/smarthome_execute_2fa_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf", 3 | "inputs": [{ 4 | "intent": "action.devices.EXECUTE", 5 | "payload": { 6 | "commands": [{ 7 | "devices": [{ 8 | "id": "123" 9 | },{ 10 | "id": "456" 11 | }], 12 | "execution": [{ 13 | "command": "action.devices.commands.OnOff", 14 | "params": { 15 | "on": true 16 | }, 17 | "challenge": { 18 | "pin": "333222" 19 | } 20 | }, { 21 | "command": "action.devices.commands.OnOff", 22 | "params": { 23 | "on": false 24 | }, 25 | "challenge": { 26 | "ack": true 27 | } 28 | }] 29 | }] 30 | } 31 | }] 32 | } -------------------------------------------------------------------------------- /src/test/resources/smarthome_execute_2fa_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload":{ 3 | "commands":[{ 4 | "ids":["123"], 5 | "status":"SUCCESS", 6 | "states":{ 7 | "online":true, 8 | "on":true 9 | } 10 | },{ 11 | "challengeNeeded": { 12 | "type": "ackNeeded" 13 | }, 14 | "ids":["456"], 15 | "errorCode":"challengeNeeded", 16 | "status":"ERROR" 17 | }] 18 | }, 19 | "requestId":"ff36a3cc-ec34-11e6-b1a0-64510650abcf" 20 | } -------------------------------------------------------------------------------- /src/test/resources/smarthome_execute_customdata_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf", 3 | "inputs": [{ 4 | "intent": "action.devices.EXECUTE", 5 | "payload": { 6 | "commands": [{ 7 | "devices": [{ 8 | "id": "123", 9 | "customData": { 10 | "fooValue": 74, 11 | "barValue": true, 12 | "bazValue": "sheepdip" 13 | } 14 | },{ 15 | "id": "456", 16 | "customData": { 17 | "fooValue": 36, 18 | "barValue": false, 19 | "bazValue": "moarsheep" 20 | } 21 | }], 22 | "execution": [{ 23 | "command": "action.devices.commands.OnOff", 24 | "params": { 25 | "on": true 26 | } 27 | }] 28 | }] 29 | } 30 | }] 31 | } -------------------------------------------------------------------------------- /src/test/resources/smarthome_execute_dock_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "inputs": [{ 3 | "context": { 4 | "locale_country": "US", 5 | "locale_language": "en" 6 | }, 7 | "intent": "action.devices.EXECUTE", 8 | "payload": { 9 | "commands": [{ 10 | "devices": [{ 11 | "id": "vacuumJawn" 12 | }], 13 | "execution": [{ 14 | "command": "action.devices.commands.Dock" 15 | }] 16 | }] 17 | } 18 | }], 19 | "requestId": "6209328299679820472" 20 | } -------------------------------------------------------------------------------- /src/test/resources/smarthome_execute_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf", 3 | "inputs": [{ 4 | "intent": "action.devices.EXECUTE", 5 | "payload": { 6 | "commands": [{ 7 | "devices": [{ 8 | "id": "123" 9 | },{ 10 | "id": "456" 11 | }], 12 | "execution": [{ 13 | "command": "action.devices.commands.OnOff", 14 | "params": { 15 | "on": true 16 | } 17 | }] 18 | }] 19 | } 20 | }] 21 | } -------------------------------------------------------------------------------- /src/test/resources/smarthome_execute_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload":{ 3 | "commands":[{ 4 | "ids":["123"], 5 | "status":"SUCCESS", 6 | "states":{ 7 | "online":true, 8 | "on":true 9 | } 10 | },{ 11 | "ids":["456"], 12 | "errorCode":"deviceTurnedOff", 13 | "status":"ERROR" 14 | }] 15 | }, 16 | "requestId":"ff36a3cc-ec34-11e6-b1a0-64510650abcf" 17 | } -------------------------------------------------------------------------------- /src/test/resources/smarthome_query_customdata_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf", 3 | "inputs": [{ 4 | "intent": "action.devices.QUERY", 5 | "payload": { 6 | "devices": [{ 7 | "id": "123", 8 | "customData": { 9 | "fooValue": 74, 10 | "barValue": true, 11 | "bazValue": "foo" 12 | } 13 | },{ 14 | "id": "456", 15 | "customData": { 16 | "fooValue": 12, 17 | "barValue": false, 18 | "bazValue": "bar" 19 | } 20 | }] 21 | } 22 | }] 23 | } -------------------------------------------------------------------------------- /src/test/resources/smarthome_query_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf", 3 | "inputs": [{ 4 | "intent": "action.devices.QUERY", 5 | "payload": { 6 | "devices": [{ 7 | "id": "123" 8 | },{ 9 | "id": "456" 10 | }] 11 | } 12 | }] 13 | } -------------------------------------------------------------------------------- /src/test/resources/smarthome_query_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload":{ 3 | "devices":{ 4 | "456":{ 5 | "online":true, 6 | "brightness":80, 7 | "color":{ 8 | "name":"cerulean", 9 | "spectrumRGB":31655 10 | }, 11 | "on":true 12 | }, 13 | "123":{ 14 | "online":true, 15 | "on":true 16 | } 17 | } 18 | }, 19 | "requestId":"ff36a3cc-ec34-11e6-b1a0-64510650abcf" 20 | } -------------------------------------------------------------------------------- /src/test/resources/smarthome_sync_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf", 3 | "inputs": [{ 4 | "intent": "action.devices.SYNC" 5 | }] 6 | } -------------------------------------------------------------------------------- /src/test/resources/smarthome_sync_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload": { 3 | "agentUserId":"1836.15267389", 4 | "devices":[{ 5 | "traits":[ 6 | "action.devices.traits.OnOff" 7 | ], 8 | "roomHint":"kitchen", 9 | "name":{ 10 | "defaultNames":[ 11 | "My Outlet 1234" 12 | ], 13 | "name":"Night light", 14 | "nicknames":[ 15 | "wall plug" 16 | ]}, 17 | "customData":{"fooValue": 74,"bazValue": "foo","barValue": true}, 18 | "id":"123", 19 | "type":"action.devices.types.OUTLET", 20 | "deviceInfo":{ 21 | "swVersion":"11.4", 22 | "model":"hs1234", 23 | "manufacturer":"lights-out-inc", 24 | "hwVersion":"3.2" 25 | } 26 | },{ 27 | "traits":[ 28 | "action.devices.traits.OnOff", 29 | "action.devices.traits.Brightness", 30 | "action.devices.traits.ColorSetting" 31 | ], 32 | "roomHint":"office", 33 | "name":{ 34 | "defaultNames":[ 35 | "lights out inc. bulb A19 color hyperglow" 36 | ], 37 | "name":"lamp1", 38 | "nicknames":[ 39 | "reading lamp" 40 | ] 41 | }, 42 | "customData":{"fooValue":12,"bazValue": "bar","barValue":false}, 43 | "id":"456", 44 | "type":"action.devices.types.LIGHT", 45 | "deviceInfo":{ 46 | "swVersion":"5.4", 47 | "model":"hg11", 48 | "manufacturer":"lights out inc.", 49 | "hwVersion":"1.2" 50 | } 51 | },{ 52 | "traits": [ 53 | "action.devices.traits.OnOff", 54 | "action.devices.traits.FanSpeed" 55 | ], 56 | "willReportState": true, 57 | "name": { 58 | "defaultNames": [ 59 | "Sirius Cybernetics Corporation 33321" 60 | ], 61 | "name": "AC Unit", 62 | "nicknames": [ 63 | "living room AC" 64 | ] 65 | }, 66 | "attributes": { 67 | "availableFanSpeeds": { 68 | "ordered": true, 69 | "speeds": [{ 70 | "speed_values": [{ 71 | "lang": "en", 72 | "speed_synonym": ["low","speed 1"] 73 | }], 74 | "speed_name": "S1" 75 | }, 76 | { 77 | "speed_values": [{ 78 | "lang": "en", 79 | "speed_synonym": ["high","speed 2"] 80 | }], 81 | "speed_name": "S2" 82 | }] 83 | } 84 | }, 85 | "customData": {"fooValue":74,"bazValue":"lambtwirl","barValue":true}, 86 | "id": "789", 87 | "type": "action.devices.types.AC_UNIT", 88 | "deviceInfo": { 89 | "swVersion": "11.4", 90 | "model": "492134", 91 | "manufacturer": "Sirius Cybernetics Corporation", 92 | "hwVersion": "3.2" 93 | } 94 | }] 95 | }, 96 | "requestId":"ff36a3cc-ec34-11e6-b1a0-64510650abcf" 97 | } 98 | -------------------------------------------------------------------------------- /src/test/resources/smarthome_sync_response_local.json: -------------------------------------------------------------------------------- 1 | { 2 | "payload": { 3 | "agentUserId":"1836.15267389", 4 | "devices":[{ 5 | "traits":[ 6 | "action.devices.traits.OnOff" 7 | ], 8 | "roomHint":"kitchen", 9 | "otherDeviceIds":[ 10 | {"deviceId":"otherDevice123"}, 11 | {"deviceId":"otherDevice123-2"} 12 | ], 13 | "name":{ 14 | "defaultNames":[ 15 | "My Outlet 1234" 16 | ], 17 | "name":"Night light", 18 | "nicknames":[ 19 | "wall plug" 20 | ]}, 21 | "customData":{"fooValue": 74,"bazValue": "foo","barValue": true}, 22 | "id":"123", 23 | "type":"action.devices.types.OUTLET", 24 | "deviceInfo": { 25 | "swVersion": "11.4", 26 | "model": "hs1234", 27 | "manufacturer": "lights-out-inc", 28 | "hwVersion": "3.2" 29 | } 30 | }] 31 | }, 32 | "requestId":"ff36a3cc-ec34-11e6-b1a0-64510650abcf" 33 | } 34 | -------------------------------------------------------------------------------- /src/test/test.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 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 | --------------------------------------------------------------------------------