├── .gitignore ├── LICENSE.md ├── README.md ├── art └── actions_web_simulator.png ├── build.gradle ├── gactions-sample ├── action.json ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── frogermcs │ │ └── gactions │ │ └── sample │ │ ├── ActionsServlet.java │ │ ├── AppEngineResponseHandler.java │ │ ├── MainRequestHandler.java │ │ ├── MainRequestHandlerFactory.java │ │ ├── MyPermissionRequestHandler.java │ │ ├── MyPermissionRequestHandlerFactory.java │ │ ├── TextRequestHandler.java │ │ └── TextRequestHandlerFactory.java │ └── webapp │ └── WEB-INF │ ├── appengine-web.xml │ └── web.xml ├── gactions ├── build.gradle └── src │ ├── main │ └── java │ │ └── com │ │ └── frogermcs │ │ └── gactions │ │ ├── AssistantActions.java │ │ ├── AssistantUtils.java │ │ ├── ResponseBuilder.java │ │ ├── ResponseHandler.java │ │ └── api │ │ ├── ActionsConfig.java │ │ ├── AssistantIntents.java │ │ ├── RequestHandler.java │ │ ├── StandardIntents.java │ │ ├── SupportedPermissions.java │ │ ├── permission │ │ └── PermissionRequestHandler.java │ │ ├── request │ │ ├── Argument.java │ │ ├── Conversation.java │ │ ├── ConversationType.java │ │ ├── Coordinates.java │ │ ├── Device.java │ │ ├── InputType.java │ │ ├── Inputs.java │ │ ├── Location.java │ │ ├── PostalAddress.java │ │ ├── RawInput.java │ │ ├── RootRequest.java │ │ ├── Time.java │ │ ├── User.java │ │ └── UserProfile.java │ │ └── response │ │ ├── AndroidApp.java │ │ ├── BasicCard.java │ │ ├── Button.java │ │ ├── ExpectedInputs.java │ │ ├── ExpectedIntent.java │ │ ├── FinalResponse.java │ │ ├── Image.java │ │ ├── ImageDisplayOptions.java │ │ ├── InputPrompt.java │ │ ├── InputValueData.java │ │ ├── OpenUrlAction.java │ │ ├── PermissionValueSpec.java │ │ ├── ResponseMetadata.java │ │ ├── RichInitialPrompt.java │ │ ├── RichInitialPromptItem.java │ │ ├── RootResponse.java │ │ ├── SimpleResponse.java │ │ ├── SpeechResponse.java │ │ ├── Status.java │ │ ├── SuggestionResponse.java │ │ ├── UrlTypeHint.java │ │ └── VersionFilter.java │ └── test │ └── java │ └── com │ └── frogermcs │ └── gactions │ ├── AssistantActionsTest.java │ ├── AssistantUtilsTest.java │ ├── ResponseBuilderTest.java │ ├── api │ └── permission │ │ └── PermissionRequestHandlerTest.java │ └── testUtils │ └── TestObjects.java ├── gradle.properties ├── gradle ├── gradle-bintray-push.gradle └── wrapper │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 2 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 3 | 4 | # User-specific stuff: 5 | .idea/workspace.xml 6 | .idea/tasks.xml 7 | 8 | # Sensitive or high-churn files: 9 | .idea/dataSources/ 10 | .idea/dataSources.ids 11 | .idea/dataSources.xml 12 | .idea/dataSources.local.xml 13 | .idea/sqlDataSources.xml 14 | .idea/dynamic.xml 15 | .idea/uiDesigner.xml 16 | 17 | # Gradle: 18 | .idea/gradle.xml 19 | .idea/libraries 20 | 21 | # Mongo Explorer plugin: 22 | .idea/mongoSettings.xml 23 | 24 | ## File-based project format: 25 | *.iws 26 | 27 | ## Plugin-specific files: 28 | 29 | # IntelliJ 30 | /out/ 31 | 32 | # mpeltonen/sbt-idea plugin 33 | .idea_modules/ 34 | 35 | # JIRA plugin 36 | atlassian-ide-plugin.xml 37 | 38 | # Crashlytics plugin (for Android Studio and IntelliJ) 39 | com_crashlytics_export_strings.xml 40 | crashlytics.properties 41 | crashlytics-build.properties 42 | fabric.properties 43 | 44 | ### Java 45 | 46 | *.class 47 | 48 | # BlueJ files 49 | *.ctxt 50 | 51 | # Mobile Tools for Java (J2ME) 52 | .mtj.tmp/ 53 | 54 | # Package Files # 55 | *.jar 56 | *.war 57 | *.ear 58 | 59 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 60 | hs_err_pid* 61 | 62 | # Google App Engine generated folder 63 | appengine-generated/ 64 | 65 | # Gradle files 66 | .gradle/ 67 | build/ 68 | .idea 69 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2017 Miroslaw Stanek 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | This project is now deprecated. Google has created their Java library for Google Actions, which I recommend to use. Thanks for your support! 3 | 4 | https://medium.com/google-developers/announcing-the-java-kotlin-client-library-for-actions-on-google-217828dead 5 | 6 | https://github.com/actions-on-google/actions-on-google-java 7 | 8 | # Unofficial Google Actions Java SDK 9 | 10 | Official [Google Actions SDK](https://github.com/actions-on-google/actions-on-google-nodejs) is written in Node.js. But in many situations voice interfaces like Google Home or Google Assistant will extend or replace mobile apps. If you are old fashioned Android engineer and the most of your code is already written in Java, why not reuse it and build voice extension to app on your own? And this is the main reason to build Google Actions Java SDK - enabling as much developers as possible to build their brilliant ideas for Google Assistant and Home. 11 | 12 | Currently this is just working proof of concept of Google Actions Java SDK. It means that there is no documentation, fixed interface, (not much) unit tests and many, many others. 13 | 14 | Google Actions Java SDK is build based on official Node.js library, but it's not a mirror copy of it. The goal is to make it fully compatible with [Conversational Protocol](https://developers.google.com/actions/reference/conversation) of Assistant Platform. 15 | 16 | ## About project 17 | Project is split into two modules: 18 | 19 | ### Assistant Actions Java SDK 20 | This code is responsible for handling requests and responses compatible with [Conversational Protocol](https://developers.google.com/actions/reference/conversation). 21 | 22 | ### Assistant Actions Java Sample 23 | Example projects showing how Assistant Actions SDK can be used in AppEngine Java project. For now Servlet is able to greet user and repeat utterance. 24 | 25 | ## How to work with limited SDK 26 | 27 | Even if it's very early stage project and there is not much utils in it, entire communication with Google Actions is based on proper responses. So even if you find any limitations you can always build `RootResponse` object by hand which in a moment of writing this is fully compatible with [Conversational Protocol](https://developers.google.com/actions/reference/conversation). Same with `RootRequest` - object should reflect all data which Google Actions send to us. 28 | 29 | ## Download 30 | 31 | Library can be downloaded from jCenter: 32 | 33 | ```gradle 34 | repositories { 35 | jcenter() 36 | } 37 | 38 | dependencies { 39 | compile 'com.frogermcs.gactions:gactions:0.3.1' 40 | } 41 | ``` 42 | 43 | ## Code 44 | 45 | Here is example code showing how to use Google Actions Java SDK 46 | 47 | ```java 48 | AssistantActions assistantActions = 49 | new AssistantActions.Builder(new AppEngineResponseHandler(response)) 50 | .addRequestHandlerFactory(StandardIntents.MAIN, new MainRequestHandlerFactory()) 51 | .addRequestHandlerFactory(StandardIntents.TEXT, new TextRequestHandlerFactory()) 52 | .addRequestHandlerFactory(StandardIntents.PERMISSION, new MyPermissionRequestHandlerFactory()) 53 | .build(); 54 | 55 | assistantActions.handleRequest(request); 56 | ``` 57 | 58 | To build `AssistantActions` object, we need to pass implementation of `ResponseHandler` interface. This class will be responsible for passing json response to Google Actions. 59 | Then we need to build intents mapping to delegate request to proper `RequestHandlers`. RequestHandlers are responsible for preparing response for Google Actions. 60 | At the end we need to pass request coming from Google Actions to our `AssistantActions` object. 61 | 62 | ### Example implementation 63 | 64 | Here are the most important classes from example AppEngine Java implementation 65 | 66 | `ActionsServlet` - entry class in our endpoint. 67 | 68 | ```java 69 | public class ActionsServlet extends HttpServlet { 70 | 71 | @Override 72 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 73 | AssistantActions assistantActions = 74 | new AssistantActions.Builder(new AppEngineResponseHandler(response)) 75 | .addRequestHandlerFactory(StandardIntents.MAIN, new MainRequestHandlerFactory()) 76 | .addRequestHandlerFactory(StandardIntents.TEXT, new TextRequestHandlerFactory()) 77 | .addRequestHandlerFactory(StandardIntents.PERMISSION, new MyPermissionRequestHandlerFactory()) 78 | .build(); 79 | 80 | assistantActions.handleRequest(parseActionRequest(request)); 81 | } 82 | 83 | //... 84 | } 85 | ``` 86 | 87 | `AppEngineResponseHandler` - implementation or `ResponseHandler`. Method `onResponse(RootResponse rootResponse)` passes back prepared response to HttpServletResponse. 88 | 89 | ```java 90 | public class AppEngineResponseHandler implements ResponseHandler { 91 | private final HttpServletResponse httpServletResponse; 92 | 93 | @Override 94 | public void onPrepareContentType(String contentType) { 95 | httpServletResponse.setContentType(contentType); 96 | } 97 | 98 | @Override 99 | public void onPrepareResponseHeaders(Map headers) { 100 | for (String headerName : headers.keySet()) { 101 | httpServletResponse.addHeader(headerName, headers.get(headerName)); 102 | } 103 | } 104 | 105 | @Override 106 | public void onResponse(RootResponse rootResponse) { 107 | gson.toJson(rootResponse, httpServletResponse.getWriter()); 108 | } 109 | } 110 | 111 | ``` 112 | 113 | `MainRequestHandler` - In response to initial Intent `assistant.intent.action.MAIN` we are asking user for NAME permission. 114 | 115 | ```java 116 | public class MainRequestHandler extends RequestHandler { 117 | MainRequestHandler(RootRequest rootRequest) { 118 | super(rootRequest); 119 | } 120 | 121 | @Override 122 | public RootResponse getResponse() { 123 | return ResponseBuilder.askForPermissionResponse("See how permissions work", 124 | SupportedPermissions.NAME); 125 | } 126 | } 127 | ``` 128 | 129 | `MyPermissionRequestHandler` - whether permission is granted or not, we asking user to tell us something so we'll be able to repeat this response. 130 | 131 | ```java 132 | public class MyPermissionRequestHandler extends PermissionRequestHandler { 133 | 134 | MyPermissionRequestHandler(RootRequest rootRequest) { 135 | super(rootRequest); 136 | } 137 | 138 | @Override 139 | public RootResponse getResponse() { 140 | UserProfile userProfile = getUserProfile(); 141 | if (isPermissionGranted() && userProfile != null) { 142 | return ResponseBuilder.askResponse("Hey " + userProfile.given_name + ". It's nice to meet you!" + 143 | "Now tell me something so I could repeat it."); 144 | 145 | } else { 146 | return ResponseBuilder.askResponse("Hey. I don't know your name, but it's ok. :)" + 147 | "Now tell me something so I could repeat it."); 148 | } 149 | } 150 | } 151 | ``` 152 | 153 | `TextRequestHandler` - finally we're replying what user's just said. 154 | 155 | ```java 156 | public class TextRequestHandler extends RequestHandler { 157 | 158 | TextRequestHandler(RootRequest rootRequest) { 159 | super(rootRequest); 160 | } 161 | 162 | @Override 163 | public RootResponse getResponse() { 164 | return ResponseBuilder.tellResponse("You've just said: " + getRootRequest().inputs.get(0).rawInputs.get(0).query); 165 | } 166 | } 167 | ``` 168 | 169 | ## How to deploy this project to our Google Cloud 170 | 171 | This description isn't very detailed. It's pretty similar to description in [official documentation](https://developers.google.com/actions/develop/sdk/), but instead of Node.js we are using Java environment on AppEngine. 172 | 173 | ### Google Cloud 174 | 175 | - Follow steps from here: https://cloud.google.com/sdk/docs/. At the end you should have created Project, know your application id, and be able to use gcloud on your computer. 176 | 177 | ### Configuration 178 | 179 | Files to edit: 180 | - google-actions-java-sample/src/main/webapp/WEB-INF/appengine-web.xml -> Edit application_id (your project id from https://console.cloud.google.com/, if not edited it's fancy name like "mythic-origin-36343"). 181 | - google-actions-java-sample/action.json -> Edit application_id (setup endpoint where Google Actions will be able to reach your servlet) 182 | 183 | If you have Google Cloud SDK already installed on you machine, and you are ready to deploy, use this gradle task: 184 | 185 | `$ ./gradlew google-actions-java-sample:appengineDeploy` 186 | 187 | ### Testing 188 | 189 | Just visit [Web Simulator](https://developers.google.com/actions/tools/web-simulator) and start testing! 190 | 191 | ![Web Simulator](https://raw.githubusercontent.com/frogermcs/Google-Actions-Java-SDK/master/art/actions_web_simulator.png "Web Simulator") 192 | 193 | ## TODO 194 | This is very general list of things planned to do to make this project as useful as possible. Your commitment is highly appreciated! 195 | 196 | - Better project structure, code cleanup and style rules 197 | - Add ssml support to responses 198 | - API.AI support (based on official SDK) 199 | - Keep conversation context 200 | - Unit tests (at least 70% coverage) 201 | - Build and distribute as a java library 202 | 203 | 204 | ## License 205 | See LICENSE.md. 206 | -------------------------------------------------------------------------------- /art/actions_web_simulator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frogermcs/Google-Actions-Java-SDK/21521dee5dc98ad1c692c73da6f73acf085e77b9/art/actions_web_simulator.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.google.cloud.tools:appengine-gradle-plugin:+' 9 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 11 | classpath 'com.bmuschko:gradle-nexus-plugin:2.3.1' 12 | } 13 | } -------------------------------------------------------------------------------- /gactions-sample/action.json: -------------------------------------------------------------------------------- 1 | { 2 | "versionLabel": "1.0.0", 3 | "agentInfo": { 4 | "languageCode": "en-US", 5 | "projectId": "hello", 6 | "voiceName": "male_1" 7 | }, 8 | "actions": [ 9 | { 10 | "initialTrigger": { 11 | "intent": "assistant.intent.action.MAIN" 12 | }, 13 | "httpExecution": { 14 | "url": "https://application-id.appspot.com/" 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /gactions-sample/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.frogermcs' 2 | version '1.0-SNAPSHOT' 3 | 4 | apply plugin: 'java' 5 | apply plugin: 'war' 6 | apply plugin: 'com.google.cloud.tools.appengine' 7 | 8 | compileJava { 9 | targetCompatibility = JavaVersion.VERSION_1_8 10 | sourceCompatibility = JavaVersion.VERSION_1_8 11 | } 12 | 13 | repositories { 14 | maven { 15 | url 'https://maven-central.storage.googleapis.com' 16 | } 17 | maven { url 'https://dl.bintray.com/frogermcs/maven/' } 18 | 19 | mavenCentral() 20 | jcenter() 21 | } 22 | 23 | dependencies { 24 | providedCompile 'javax.servlet:servlet-api:2.5' 25 | compile project(':gactions') 26 | // compile 'com.frogermcs.gactions:gactions:0.3.1' 27 | compile 'com.google.appengine:appengine:+' 28 | testCompile group: 'junit', name: 'junit', version: '4.11' 29 | } 30 | 31 | appengine { 32 | run { 33 | port = 8080 34 | } 35 | 36 | deploy { 37 | stopPreviousVersion = true 38 | promote = true 39 | projectId = 'hello' 40 | version = '1.0.0' 41 | } 42 | } -------------------------------------------------------------------------------- /gactions-sample/src/main/java/com/frogermcs/gactions/sample/ActionsServlet.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.sample; 2 | 3 | import com.frogermcs.gactions.AssistantActions; 4 | import com.frogermcs.gactions.api.StandardIntents; 5 | import com.frogermcs.gactions.api.request.RootRequest; 6 | import com.google.gson.Gson; 7 | import com.google.gson.stream.JsonReader; 8 | 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServlet; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | import java.util.logging.Logger; 15 | 16 | /** 17 | * Created by froger_mcs on 17/01/2017. 18 | */ 19 | public class ActionsServlet extends HttpServlet { 20 | private static final Logger log = Logger.getLogger(ActionsServlet.class.getName()); 21 | 22 | @Override 23 | public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { 24 | resp.getWriter().println("Hello, world Google Actions"); 25 | } 26 | 27 | @Override 28 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 29 | log.info("POST: " + request); 30 | 31 | AssistantActions assistantActions = 32 | new AssistantActions.Builder(new AppEngineResponseHandler(response)) 33 | .addRequestHandlerFactory(StandardIntents.MAIN, new MainRequestHandlerFactory()) 34 | .addRequestHandlerFactory(StandardIntents.TEXT, new TextRequestHandlerFactory()) 35 | .addRequestHandlerFactory(StandardIntents.PERMISSION, new MyPermissionRequestHandlerFactory()) 36 | .build(); 37 | 38 | assistantActions.handleRequest(parseActionRequest(request)); 39 | } 40 | 41 | private RootRequest parseActionRequest(HttpServletRequest request) throws IOException { 42 | JsonReader jsonReader = new JsonReader(request.getReader()); 43 | RootRequest rootRequest = new Gson().fromJson(jsonReader, RootRequest.class); 44 | log.info("REQUEST:" + rootRequest); 45 | return rootRequest; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gactions-sample/src/main/java/com/frogermcs/gactions/sample/AppEngineResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.sample; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.frogermcs.gactions.ResponseHandler; 5 | import com.frogermcs.gactions.api.response.RootResponse; 6 | 7 | import javax.servlet.http.HttpServletResponse; 8 | import java.io.IOException; 9 | import java.util.Map; 10 | 11 | /** 12 | * Created by froger_mcs on 19/01/2017. 13 | */ 14 | public class AppEngineResponseHandler implements ResponseHandler { 15 | private final HttpServletResponse httpServletResponse; 16 | private final ObjectMapper objectMapper; 17 | 18 | public AppEngineResponseHandler(HttpServletResponse httpServletResponse) { 19 | this(httpServletResponse, new ObjectMapper()); 20 | } 21 | 22 | public AppEngineResponseHandler(HttpServletResponse httpServletResponse, ObjectMapper objectMapper) { 23 | this.httpServletResponse = httpServletResponse; 24 | this.objectMapper = objectMapper; 25 | } 26 | 27 | @Override 28 | public void onPrepareContentType(String contentType) { 29 | httpServletResponse.setContentType(contentType); 30 | } 31 | 32 | @Override 33 | public void onPrepareResponseHeaders(Map headers) { 34 | for (String headerName : headers.keySet()) { 35 | httpServletResponse.addHeader(headerName, headers.get(headerName)); 36 | } 37 | } 38 | 39 | @Override 40 | public void onResponse(RootResponse rootResponse) { 41 | try { 42 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 43 | objectMapper.writeValue(httpServletResponse.getWriter(), rootResponse); 44 | } catch (IOException e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gactions-sample/src/main/java/com/frogermcs/gactions/sample/MainRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.sample; 2 | 3 | import com.frogermcs.gactions.ResponseBuilder; 4 | import com.frogermcs.gactions.api.RequestHandler; 5 | import com.frogermcs.gactions.api.SupportedPermissions; 6 | import com.frogermcs.gactions.api.request.RootRequest; 7 | import com.frogermcs.gactions.api.response.RootResponse; 8 | 9 | /** 10 | * Created by froger_mcs on 19/01/2017. 11 | */ 12 | public class MainRequestHandler extends RequestHandler { 13 | MainRequestHandler(RootRequest rootRequest) { 14 | super(rootRequest); 15 | } 16 | 17 | @Override 18 | public RootResponse getResponse() { 19 | return ResponseBuilder.askForPermissionResponse("See how permissions work", 20 | SupportedPermissions.NAME); 21 | } 22 | } -------------------------------------------------------------------------------- /gactions-sample/src/main/java/com/frogermcs/gactions/sample/MainRequestHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.sample; 2 | 3 | import com.frogermcs.gactions.api.RequestHandler; 4 | import com.frogermcs.gactions.api.request.RootRequest; 5 | 6 | /** 7 | * Created by froger_mcs on 19/01/2017. 8 | */ 9 | public class MainRequestHandlerFactory extends RequestHandler.Factory { 10 | @Override 11 | public RequestHandler create(RootRequest rootRequest) { 12 | return new MainRequestHandler(rootRequest); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /gactions-sample/src/main/java/com/frogermcs/gactions/sample/MyPermissionRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.sample; 2 | 3 | import com.frogermcs.gactions.ResponseBuilder; 4 | import com.frogermcs.gactions.api.permission.PermissionRequestHandler; 5 | import com.frogermcs.gactions.api.request.RootRequest; 6 | import com.frogermcs.gactions.api.request.UserProfile; 7 | import com.frogermcs.gactions.api.response.RootResponse; 8 | 9 | /** 10 | * Created by froger_mcs on 29/04/2017. 11 | */ 12 | public class MyPermissionRequestHandler extends PermissionRequestHandler { 13 | 14 | MyPermissionRequestHandler(RootRequest rootRequest) { 15 | super(rootRequest); 16 | } 17 | 18 | @Override 19 | public RootResponse getResponse() { 20 | UserProfile userProfile = getUserProfile(); 21 | if (isPermissionGranted() && userProfile != null) { 22 | return ResponseBuilder.askResponse("Hey " + userProfile.given_name + ". It's nice to meet you! " + 23 | "Now tell me something so I could repeat it."); 24 | 25 | } else { 26 | return ResponseBuilder.askResponse("Hey. I don't know your name, but it's ok. :) " + 27 | "Now tell me something so I could repeat it."); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /gactions-sample/src/main/java/com/frogermcs/gactions/sample/MyPermissionRequestHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.sample; 2 | 3 | import com.frogermcs.gactions.api.RequestHandler; 4 | import com.frogermcs.gactions.api.request.RootRequest; 5 | 6 | /** 7 | * Created by froger_mcs on 29/04/2017. 8 | */ 9 | public class MyPermissionRequestHandlerFactory extends RequestHandler.Factory{ 10 | @Override 11 | public RequestHandler create(RootRequest rootRequest) { 12 | return new MyPermissionRequestHandler(rootRequest); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /gactions-sample/src/main/java/com/frogermcs/gactions/sample/TextRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.sample; 2 | 3 | import com.frogermcs.gactions.api.RequestHandler; 4 | import com.frogermcs.gactions.ResponseBuilder; 5 | import com.frogermcs.gactions.api.request.RootRequest; 6 | import com.frogermcs.gactions.api.response.RootResponse; 7 | 8 | /** 9 | * Created by froger_mcs on 19/01/2017. 10 | */ 11 | public class TextRequestHandler extends RequestHandler { 12 | 13 | TextRequestHandler(RootRequest rootRequest) { 14 | super(rootRequest); 15 | } 16 | 17 | @Override 18 | public RootResponse getResponse() { 19 | return ResponseBuilder.tellResponse("You've just said: " + getRootRequest().inputs.get(0).rawInputs.get(0).query); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gactions-sample/src/main/java/com/frogermcs/gactions/sample/TextRequestHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.sample; 2 | 3 | import com.frogermcs.gactions.api.RequestHandler; 4 | import com.frogermcs.gactions.api.request.RootRequest; 5 | 6 | /** 7 | * Created by froger_mcs on 19/01/2017. 8 | */ 9 | public class TextRequestHandlerFactory extends RequestHandler.Factory { 10 | @Override 11 | public RequestHandler create(RootRequest rootRequest) { 12 | return new TextRequestHandler(rootRequest); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /gactions-sample/src/main/webapp/WEB-INF/appengine-web.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | application-id 16 | 1 17 | true 18 | -------------------------------------------------------------------------------- /gactions-sample/src/main/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | actions_servlet 8 | com.frogermcs.gactions.sample.ActionsServlet 9 | 10 | 11 | actions_servlet 12 | / 13 | 14 | -------------------------------------------------------------------------------- /gactions/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'io.franzbecker.gradle-lombok' version '1.14' 3 | id 'java' 4 | } 5 | 6 | compileJava { 7 | targetCompatibility = JavaVersion.VERSION_1_8 8 | sourceCompatibility = JavaVersion.VERSION_1_8 9 | } 10 | 11 | repositories { 12 | maven { 13 | url 'https://maven-central.storage.googleapis.com' 14 | } 15 | mavenCentral() 16 | jcenter() 17 | } 18 | 19 | dependencies { 20 | compile 'com.google.code.findbugs:jsr305:3.0.1' 21 | compile 'com.google.code.gson:gson:2.8.0' 22 | compile 'com.fasterxml.jackson.core:jackson-annotations:2.9.5' 23 | 24 | testCompile 'junit:junit:4.12' 25 | testCompile 'org.mockito:mockito-core:2.23.0' 26 | testCompile 'org.hamcrest:hamcrest-core:1.3' 27 | testCompile 'org.hamcrest:hamcrest-library:1.3' 28 | testCompile 'org.hamcrest:hamcrest-integration:1.3' 29 | } 30 | 31 | apply from: rootProject.file('gradle/gradle-bintray-push.gradle') -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/AssistantActions.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions; 2 | 3 | import com.frogermcs.gactions.api.ActionsConfig; 4 | import com.frogermcs.gactions.api.RequestHandler; 5 | import com.frogermcs.gactions.api.request.RootRequest; 6 | import com.frogermcs.gactions.api.response.RootResponse; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * The {@code ActionsServlet} is the class that handles conversation API from Google Assistant. 13 | * Requests ({@link com.frogermcs.gactions.api.request.RootRequest} are passed to {@link com.frogermcs.gactions.api.RequestHandler} 14 | * based on action intents mapping. 15 | */ 16 | public class AssistantActions { 17 | private final ResponseHandler responseHandler; 18 | private final Map requestHandlersFactories; 19 | 20 | AssistantActions(ResponseHandler responseHandler, 21 | Map requestHandlersFactories) { 22 | this.responseHandler = responseHandler; 23 | this.requestHandlersFactories = requestHandlersFactories; 24 | } 25 | 26 | /** 27 | * Gets parsed RootRequest. If action intent was defined in {@code Builder.addRequestHandlerFactory()} request 28 | * will be passed to {@code RequestHandler} which then will generate RootResponse in {@code RequestHandler.getResponse} 29 | *

30 | * If there is no handler for action intent, {@code ResponseBuilder.tellResponse(ActionsConfig.ERROR_MESSAGE)} 31 | * is returned. 32 | * 33 | * @param rootRequest - the {@link RootResponse} object that contains request from Google Actions 34 | */ 35 | public void handleRequest(RootRequest rootRequest) { 36 | responseHandler.onPrepareContentType(ActionsConfig.HTTP_CONTENT_TYPE_JSON); 37 | responseHandler.onPrepareResponseHeaders(ActionsConfig.RESPONSE_HEADERS); 38 | 39 | String actionIntent = AssistantUtils.getActionIntent(rootRequest); 40 | if (actionIntent != null && requestHandlersFactories.containsKey(actionIntent)) { 41 | RequestHandler requestHandler = requestHandlersFactories.get(actionIntent).create(rootRequest); 42 | responseHandler.onResponse(requestHandler.getResponse()); 43 | } else { 44 | responseHandler.onResponse(ResponseBuilder.tellResponse(ActionsConfig.ERROR_MESSAGE)); 45 | } 46 | } 47 | 48 | /** 49 | * Constructs a {@code AssistantActions.Builder}. 50 | */ 51 | public static class Builder { 52 | private ResponseHandler responseHandler; 53 | 54 | private final Map requestHandlersFactories = new HashMap<>(); 55 | 56 | /** 57 | * @param responseHandler - the {@link ResponseHandler} object that handles responses from AssistantActions. 58 | * {@code RootResponse} is direct response for Google Actions API and should be passed 59 | * back as POST response. 60 | */ 61 | public Builder(ResponseHandler responseHandler) { 62 | this.responseHandler = responseHandler; 63 | } 64 | 65 | /** 66 | * Sets {@code RequestHandler.Factory} which creates {@code RequestHandler} to handle action intent. 67 | * 68 | * @param actionIntent - unique action intent to handle 69 | * @param factory - the {@link RequestHandler.Factory} object that creates {@link RequestHandler} to 70 | * handle given action intent 71 | * @return this {@code AssistantActions.Builder} 72 | */ 73 | public Builder addRequestHandlerFactory(String actionIntent, RequestHandler.Factory factory) { 74 | this.requestHandlersFactories.put(actionIntent, factory); 75 | return this; 76 | } 77 | 78 | /** 79 | * Returns an {@code AssistantActions} built from parameters set by the setter methods. 80 | * 81 | * @return 82 | */ 83 | public AssistantActions build() { 84 | return new AssistantActions(responseHandler, requestHandlersFactories); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/AssistantUtils.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions; 2 | 3 | import com.frogermcs.gactions.api.request.Inputs; 4 | import com.frogermcs.gactions.api.request.RootRequest; 5 | import com.frogermcs.gactions.api.response.RootResponse; 6 | 7 | import javax.annotation.Nullable; 8 | 9 | /** 10 | * Helper methods for Google Actions API requests/responses 11 | */ 12 | public class AssistantUtils { 13 | /** 14 | * @param rootRequest - the {@link RootResponse} object that contains request from Google Actions 15 | * @return Action intent string from Google Action request. 16 | */ 17 | @Nullable 18 | public static String getActionIntent(RootRequest rootRequest) { 19 | if (rootRequest.inputs != null && rootRequest.inputs.size() > 0) { 20 | Inputs inputs = rootRequest.inputs.get(0); 21 | return inputs.intent; 22 | } 23 | 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/ResponseBuilder.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions; 2 | 3 | import com.frogermcs.gactions.api.StandardIntents; 4 | import com.frogermcs.gactions.api.SupportedPermissions; 5 | import com.frogermcs.gactions.api.response.*; 6 | 7 | import java.util.*; 8 | 9 | /** 10 | * Response builder for Google Actions API. There are three general types of responses: Tell (text feedback for user), 11 | * Ask (asking user for additional data), AskForPermission (asking user for permissions). 12 | */ 13 | public class ResponseBuilder { 14 | 15 | public static RootResponse tellResponse(String message) { 16 | return ResponseBuilder.tellResponse(SpeechResponse.builder().textToSpeech(message).displayText(message).build()); 17 | } 18 | 19 | public static RootResponse tellResponse(SpeechResponse message) { 20 | RootResponse rootResponse = new RootResponse(); 21 | rootResponse.expectUserResponse = false; 22 | rootResponse.finalResponse = new FinalResponse(); 23 | rootResponse.finalResponse.speechResponse = message; 24 | return rootResponse; 25 | } 26 | public static RootResponse tellResponseWithRichInput(SpeechResponse message) { 27 | return ResponseBuilder.tellResponseWithRichInput(message, null, null, null); 28 | } 29 | public static RootResponse tellResponseWithRichInput(SpeechResponse message, ResponseMetadata metadata) { 30 | return ResponseBuilder.tellResponseWithRichInput(message, null, null, metadata); 31 | } 32 | public static RootResponse tellResponseWithRichInput(SpeechResponse message, String conversationToken, List suggestions) { 33 | RichInitialPromptItem richInitialPromptItem = RichInitialPromptItem.builder().simpleResponse(message).build(); 34 | return ResponseBuilder.tellResponseWithRichInput(Collections.singletonList(richInitialPromptItem), conversationToken, suggestions, null ); 35 | } 36 | public static RootResponse tellResponseWithRichInput(SpeechResponse message, String conversationToken, List suggestions, ResponseMetadata metadata) { 37 | RichInitialPromptItem richInitialPromptItem = RichInitialPromptItem.builder().simpleResponse(message).build(); 38 | return ResponseBuilder.tellResponseWithRichInput(Collections.singletonList(richInitialPromptItem), conversationToken, suggestions, metadata ); 39 | } 40 | public static RootResponse tellResponseWithRichInput(List richInitialPromptItems, ResponseMetadata metadata) { 41 | return ResponseBuilder.tellResponseWithRichInput(richInitialPromptItems, null, null, metadata); 42 | } 43 | public static RootResponse tellResponseWithRichInput(List richInitialPromptItems, String conversationToken, List suggestions, ResponseMetadata metadata) { 44 | RootResponse rootResponse = new RootResponse(); 45 | rootResponse.expectUserResponse = false; 46 | rootResponse.conversationToken = conversationToken; 47 | rootResponse.finalResponse = new FinalResponse(); 48 | 49 | RichInitialPrompt richResponse = RichInitialPrompt.builder().items(richInitialPromptItems).suggestions(suggestions).build(); 50 | 51 | rootResponse.finalResponse.richResponse = richResponse; 52 | 53 | rootResponse.responseMetadata = metadata; 54 | 55 | return rootResponse; 56 | } 57 | 58 | 59 | public static RootResponse askResponse(String message) { 60 | return ResponseBuilder.askResponse(SpeechResponse.builder().textToSpeech(message).displayText(message).build()); 61 | } 62 | 63 | public static RootResponse askResponse(SpeechResponse message) { 64 | return askResponse(message, null, null); 65 | } 66 | 67 | public static RootResponse askResponse(String message, String[] noInputPrompts) { 68 | return askResponse(message, null, noInputPrompts); 69 | } 70 | 71 | public static RootResponse askResponseWithRichInput(SpeechResponse message) { 72 | return askResponseWithRichInput(message, null, null, null, null); 73 | } 74 | public static RootResponse askResponseWithRichInput(SpeechResponse message, ResponseMetadata metadata) { 75 | return askResponseWithRichInput(message, null, null, null, metadata); 76 | } 77 | public static RootResponse askResponseWithRichInput(SpeechResponse message, String conversationToken, List noInputPrompts, List suggestions) { 78 | RichInitialPromptItem richInitialPromptItem = RichInitialPromptItem.builder().simpleResponse(message).build(); 79 | return ResponseBuilder.askResponseWithRichInput(Collections.singletonList(richInitialPromptItem), conversationToken, noInputPrompts, suggestions, null); 80 | } 81 | public static RootResponse askResponseWithRichInput(SpeechResponse message, String conversationToken, List noInputPrompts, List suggestions, ResponseMetadata metadata ) { 82 | RichInitialPromptItem richInitialPromptItem = RichInitialPromptItem.builder().simpleResponse(message).build(); 83 | return ResponseBuilder.askResponseWithRichInput(Collections.singletonList(richInitialPromptItem), conversationToken, noInputPrompts, suggestions, metadata); 84 | } 85 | public static RootResponse askResponseWithRichInput(List richInitialPromptItems) { 86 | return askResponseWithRichInput(richInitialPromptItems, null, null, null, null); 87 | } 88 | public static RootResponse askResponseWithRichInput(List richInitialPromptItems, ResponseMetadata metadata) { 89 | return askResponseWithRichInput(richInitialPromptItems, null, null, null, metadata); 90 | } 91 | public static RootResponse askResponseWithRichInput(List richInitialPromptItems, String conversationToken, List noInputPrompts, List suggestions, ResponseMetadata metadata) { 92 | 93 | RootResponse rootResponse = new RootResponse(); 94 | rootResponse.expectUserResponse = true; 95 | rootResponse.conversationToken = conversationToken; 96 | rootResponse.expectedInputs = new ArrayList<>(); 97 | 98 | ExpectedInputs expectedInput = new ExpectedInputs(); 99 | expectedInput.inputPrompt = new InputPrompt(); 100 | 101 | RichInitialPrompt richInitialPrompt = RichInitialPrompt.builder().items(richInitialPromptItems).suggestions(suggestions).build(); 102 | 103 | expectedInput.inputPrompt.richInitialPrompt = richInitialPrompt; 104 | 105 | if (noInputPrompts != null && noInputPrompts.size() > 0) { 106 | expectedInput.inputPrompt.noInputPrompts = noInputPrompts; 107 | } 108 | 109 | expectedInput.possibleIntents = new ArrayList<>(); 110 | expectedInput.possibleIntents.add(new ExpectedIntent(StandardIntents.TEXT)); 111 | 112 | rootResponse.expectedInputs.add(expectedInput); 113 | 114 | rootResponse.responseMetadata = metadata; 115 | 116 | return rootResponse; 117 | } 118 | 119 | /** 120 | * @param message - speech response for Google Actions request 121 | * @param conversationToken 122 | * @param noInputPrompts 123 | * @return {@link RootResponse} object that uses speech response to ask user for additional data 124 | */ 125 | public static RootResponse askResponse(SpeechResponse message, String conversationToken, List noInputPrompts ) { 126 | RootResponse rootResponse = new RootResponse(); 127 | rootResponse.expectUserResponse = true; 128 | rootResponse.conversationToken = conversationToken; 129 | rootResponse.expectedInputs = new ArrayList<>(); 130 | 131 | ExpectedInputs expectedInput = new ExpectedInputs(); 132 | expectedInput.inputPrompt = new InputPrompt(); 133 | expectedInput.inputPrompt.initialPrompts = Collections.singletonList(message); 134 | 135 | if (noInputPrompts != null && noInputPrompts.size() > 0) { 136 | expectedInput.inputPrompt.noInputPrompts = noInputPrompts; 137 | } 138 | 139 | expectedInput.possibleIntents = new ArrayList<>(); 140 | expectedInput.possibleIntents.add(new ExpectedIntent(StandardIntents.TEXT)); 141 | 142 | rootResponse.expectedInputs.add(expectedInput); 143 | return rootResponse; 144 | } 145 | 146 | public static RootResponse askResponse(String message, String conversationToken, String[] noInputPrompts) { 147 | List noInputPromptsConverted = null; 148 | if (noInputPrompts != null && noInputPrompts.length > 0) { 149 | List result = new ArrayList<>(); 150 | for(String noInputPrompt : noInputPrompts) { 151 | noInputPromptsConverted.add(SpeechResponse.builder().textToSpeech(noInputPrompt).displayText(noInputPrompt).build()); 152 | } 153 | } 154 | 155 | return ResponseBuilder.askResponse(SpeechResponse.builder().textToSpeech(message).displayText(message).build(), conversationToken, noInputPromptsConverted); 156 | } 157 | 158 | /** 159 | * @param permissionContext - Context why the permission is being asked; it's the TTS 160 | * prompt prefix (action phrase) we ask the user. 161 | * @param permissions - Array of permissions Assistant supports, each of 162 | * which comes from Assistant.SupportedPermissions. 163 | * @return {@link RootResponse} object that is sent to Assistant to ask for the user's permission 164 | * @throws IllegalArgumentException when permissionContext or permissions are null or empty. Exception is also 165 | * thrown when any of given permission isn't one of: [NAME, DEVICE_PRECISE_LOCATION, DEVICE_COARSE_LOCATION] 166 | */ 167 | public static RootResponse askForPermissionResponse(String permissionContext, SupportedPermissions... permissions) { 168 | List permissionsStr = new ArrayList<>(); 169 | for (SupportedPermissions permission : permissions) { 170 | permissionsStr.add(permission.name()); 171 | } 172 | 173 | return askForPermissionResponse(permissionContext, permissionsStr); 174 | } 175 | 176 | /** 177 | * @param permissionContext - Context why the permission is being asked; it's the TTS 178 | * prompt prefix (action phrase) we ask the user. 179 | * @param permissions - Array of permissions Assistant supports, each of 180 | * which comes from Assistant.SupportedPermissions. 181 | * @return {@link RootResponse} object that is sent to Assistant to ask for the user's permission 182 | * @throws IllegalArgumentException when permissionContext or permissions are null or empty. Exception is also 183 | * thrown when any of given permission isn't one of: [NAME, DEVICE_PRECISE_LOCATION, DEVICE_COARSE_LOCATION] 184 | */ 185 | public static RootResponse askForPermissionResponse(String permissionContext, String... permissions) { 186 | return askForPermissionResponse(permissionContext, Arrays.asList(permissions)); 187 | } 188 | 189 | /** 190 | * @param permissionContext - Context why the permission is being asked; it's the TTS 191 | * prompt prefix (action phrase) we ask the user. 192 | * @param permissions - Array of permissions Assistant supports, each of 193 | * which comes from Assistant.SupportedPermissions. 194 | * @return {@link RootResponse} object that is sent to Assistant to ask for the user's permission 195 | * @throws IllegalArgumentException when permissionContext or permissions are null or empty. Exception is also 196 | * thrown when any of given permission isn't one of: [NAME, DEVICE_PRECISE_LOCATION, DEVICE_COARSE_LOCATION] 197 | */ 198 | public static RootResponse askForPermissionResponse(String permissionContext, Collection permissions) throws IllegalArgumentException { 199 | if (permissionContext == null || permissionContext.length() == 0) { 200 | throw new IllegalArgumentException("permissionContext argument cannot be null"); 201 | } 202 | 203 | if (permissions == null || permissions.isEmpty()) { 204 | throw new IllegalArgumentException("At least one permission needed."); 205 | } 206 | 207 | try { 208 | for (String permission : permissions) { 209 | SupportedPermissions.valueOf(permission); 210 | } 211 | } catch (IllegalArgumentException e) { 212 | throw new IllegalArgumentException("Assistant permission must be one of [NAME, DEVICE_PRECISE_LOCATION, DEVICE_COARSE_LOCATION, EMAIL]"); 213 | } 214 | 215 | RootResponse rootResponse = new RootResponse(); 216 | rootResponse.expectUserResponse = true; 217 | 218 | InputValueData inputValueData = new InputValueData("type.googleapis.com/google.actions.v2.PermissionValueSpec", permissionContext, new ArrayList<>(permissions)); 219 | 220 | ExpectedIntent expectedIntent = new ExpectedIntent(StandardIntents.PERMISSION); 221 | expectedIntent.inputValueData = inputValueData; 222 | 223 | ExpectedInputs expectedInput = new ExpectedInputs(); 224 | expectedInput.possibleIntents = new ArrayList<>(); 225 | expectedInput.possibleIntents.add(expectedIntent); 226 | 227 | rootResponse.expectedInputs = new ArrayList<>(); 228 | rootResponse.expectedInputs.add(expectedInput); 229 | 230 | return rootResponse; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/ResponseHandler.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions; 2 | 3 | import com.frogermcs.gactions.api.response.RootResponse; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * The {@code ResponseHandler} is the interface for class which handles responses for Google Actions API. 9 | * {@link com.frogermcs.gactions.AssistantActions} passes generated responses ({@link RootResponse}) which then 10 | * should be passed back as response for POST request from Google Assistant. 11 | */ 12 | public interface ResponseHandler { 13 | /** 14 | * Set proper contentType for POST request for Google Actions API 15 | * 16 | * @param contentType - contentType defined in {@code ActionsConfig.HTTP_CONTENT_TYPE_JSON} 17 | */ 18 | void onPrepareContentType(String contentType); 19 | 20 | /** 21 | * Set headers for POST request for Google Actions API 22 | * 23 | * @param headers - headers defined in {@code ActionsConfig.RESPONSE_HEADERS} 24 | */ 25 | void onPrepareResponseHeaders(Map headers); 26 | 27 | /** 28 | * Response for POST request from Google Actions API 29 | * 30 | * @param rootResponse - the {@link RootResponse} object that contains response for Google Actions API request 31 | */ 32 | void onResponse(RootResponse rootResponse); 33 | } 34 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/ActionsConfig.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Google Actions API config 8 | */ 9 | public class ActionsConfig { 10 | /** 11 | * Default tell-response for intent actions which aren't handled by user RequestHandler - Intent Action mapping 12 | */ 13 | public static final String ERROR_MESSAGE = "Sorry, I am unable to process your request."; 14 | /** 15 | * Header configurations for Google Actions API 16 | */ 17 | public static final String CONVERSATION_API_VERSION_HEADER = "Google-Assistant-API-Version"; 18 | public static final String CONVERSATION_API_VERSION = "v1"; 19 | public static final String HTTP_CONTENT_TYPE_JSON = "application/json"; 20 | public static final Map RESPONSE_HEADERS; 21 | 22 | static { 23 | RESPONSE_HEADERS = new HashMap<>(); 24 | RESPONSE_HEADERS.put(CONVERSATION_API_VERSION_HEADER, CONVERSATION_API_VERSION); 25 | } 26 | 27 | /** 28 | * Permission granted argument. 29 | */ 30 | public static final String ARG_PERMISSION_GRANTED = "permission_granted"; 31 | } 32 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/AssistantIntents.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api; 2 | 3 | /** 4 | * Standard action intents handled by Google Assistant 5 | */ 6 | public class AssistantIntents { 7 | /** 8 | * Assistant fires MAIN intent for queries like [talk to $action]. 9 | */ 10 | public static final String MAIN = "assistant.intent.action.MAIN"; 11 | /** 12 | * Assistant fires TEXT intent when action issues ask intent. 13 | */ 14 | public static final String TEXT = "assistant.intent.action.TEXT"; 15 | /** 16 | * Assistant fires PERMISSION intent when action invokes askForPermission. 17 | */ 18 | public static final String PERMISSION = "assistant.intent.action.PERMISSION"; 19 | } 20 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/RequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api; 2 | 3 | import com.frogermcs.gactions.api.request.RootRequest; 4 | import com.frogermcs.gactions.api.response.RootResponse; 5 | 6 | /** 7 | * The {@code RequestHandler} is the class which handles Google Actions API requests. It should be used in 8 | * {@link com.frogermcs.gactions.AssistantActions} mappings to handle action intents. 9 | */ 10 | public abstract class RequestHandler { 11 | 12 | private final RootRequest rootRequest; 13 | 14 | protected RequestHandler(RootRequest rootRequest) { 15 | this.rootRequest = rootRequest; 16 | } 17 | 18 | /** 19 | * @return the {@link RootRequest} object - Google Actions API request 20 | */ 21 | public RootRequest getRootRequest() { 22 | return rootRequest; 23 | } 24 | 25 | /** 26 | * @return UserId from Google Actions API request 27 | */ 28 | public String getUserId() { 29 | return rootRequest.user.userId; 30 | } 31 | 32 | /** 33 | * @return the {@link RootResponse} object, response for Google Actions API request 34 | */ 35 | public abstract RootResponse getResponse(); 36 | 37 | public static abstract class Factory { 38 | /** 39 | * @param rootRequest - the {@link RootResponse} object that contains request from Google Actions 40 | * @return the {@link RequestHandler} object, handler for Google Actions API requests 41 | */ 42 | public abstract RequestHandler create(RootRequest rootRequest); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/StandardIntents.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api; 2 | 3 | /** 4 | * Standard action intents handled by Google Assistant 5 | */ 6 | public class StandardIntents { 7 | /** 8 | * Assistant fires MAIN intent for queries like [talk to $action]. 9 | */ 10 | public static final String MAIN = "actions.intent.MAIN"; 11 | /** 12 | * Assistant fires TEXT intent when action issues ask intent. 13 | */ 14 | public static final String TEXT = "actions.intent.TEXT"; 15 | /** 16 | * Assistant fires PERMISSION intent when action invokes askForPermission. 17 | */ 18 | public static final String PERMISSION = "actions.intent.PERMISSION"; 19 | } 20 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/SupportedPermissions.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api; 2 | 3 | /** 4 | * Permissions supported by Google Assistant API 5 | */ 6 | public enum SupportedPermissions { 7 | /** 8 | * The user's name as defined in the https://developers.google.com/actions/reference/conversation#UserProfile|UserProfile object 9 | */ 10 | NAME, 11 | /** 12 | * The location of the user's current device, as defined in the https://developers.google.com/actions/reference/conversation#Location|Location object. 13 | */ 14 | DEVICE_PRECISE_LOCATION, 15 | /** 16 | * City and zipcode corresponding to the location of the user's current device, as defined in the https://developers.google.com/actions/reference/conversation#Location|Location object. 17 | */ 18 | DEVICE_COARSE_LOCATION, 19 | /** 20 | * Available in userprofile but not yet documented on https://developers.google.com 21 | */ 22 | EMAIL 23 | } 24 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/permission/PermissionRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.permission; 2 | 3 | import com.frogermcs.gactions.AssistantUtils; 4 | import com.frogermcs.gactions.api.ActionsConfig; 5 | import com.frogermcs.gactions.api.RequestHandler; 6 | import com.frogermcs.gactions.api.StandardIntents; 7 | import com.frogermcs.gactions.api.request.Argument; 8 | import com.frogermcs.gactions.api.request.Location; 9 | import com.frogermcs.gactions.api.request.RootRequest; 10 | import com.frogermcs.gactions.api.request.UserProfile; 11 | 12 | import javax.annotation.Nullable; 13 | 14 | /** 15 | * The {@code PermissionRequestHandler} is the class which handles Google Actions API permission requests. 16 | */ 17 | public abstract class PermissionRequestHandler extends RequestHandler { 18 | protected PermissionRequestHandler(RootRequest rootRequest) { 19 | super(rootRequest); 20 | } 21 | 22 | /** 23 | * If action intent from {@link RootRequest} is {@code StandardIntents.PERMISSION}, then return 24 | * information whether permission was granted or not. 25 | *

26 | * Otherwise returns {@code false}. 27 | * 28 | * @return boolean value for permission 29 | */ 30 | public boolean isPermissionGranted() { 31 | RootRequest rootRequest = getRootRequest(); 32 | if (StandardIntents.PERMISSION.equals(AssistantUtils.getActionIntent(rootRequest))) { 33 | for (Argument argument : rootRequest.inputs.get(0).arguments) { 34 | if (ActionsConfig.ARG_PERMISSION_GRANTED.equals(argument.name)) { 35 | return Boolean.valueOf(argument.text_value); 36 | } 37 | } 38 | 39 | } 40 | 41 | return false; 42 | } 43 | 44 | /** 45 | * @return {@link UserProfile} object if permission was granted and object exists in Google Actions request 46 | */ 47 | @Nullable 48 | public UserProfile getUserProfile() { 49 | if (isPermissionGranted()) { 50 | return getRootRequest().user.profile; 51 | } else { 52 | return null; 53 | } 54 | } 55 | 56 | /** 57 | * @return {@link Location} object if permission was granted and object exists in Google Actions request 58 | */ 59 | @Nullable 60 | public Location getLocation() { 61 | if (isPermissionGranted()) { 62 | return getRootRequest().device.location; 63 | } else { 64 | return null; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/Argument.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | 3 | import lombok.*; 4 | 5 | @Builder 6 | @EqualsAndHashCode 7 | @ToString 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class Argument { 11 | public String name; 12 | public String raw_text; 13 | public String int_value; 14 | public String bool_value; 15 | public String text_value; 16 | public String date_value; 17 | public String time_value; 18 | public Location location_value; 19 | public String formatted_address; 20 | } 21 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/Conversation.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | 3 | import lombok.*; 4 | 5 | /** 6 | * Created by froger_mcs on 17/01/2017. 7 | */ 8 | @Builder 9 | @EqualsAndHashCode 10 | @ToString 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class Conversation { 14 | public String conversationId; 15 | public ConversationType type; 16 | public String conversationToken; 17 | } 18 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/ConversationType.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | 3 | /** 4 | * Created by froger_mcs on 17/01/2017. 5 | */ 6 | public enum ConversationType { 7 | TYPE_UNSPECIFIED, 8 | NEW, 9 | ACTIVE, 10 | EXPIRED, 11 | ARCHIVED 12 | } 13 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/Coordinates.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | 3 | import lombok.*; 4 | 5 | /** 6 | * Created by froger_mcs on 17/01/2017. 7 | */ 8 | @Builder 9 | @EqualsAndHashCode 10 | @ToString 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class Coordinates { 14 | public double latitude; 15 | public double longitude; 16 | } 17 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/Device.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | 3 | import lombok.*; 4 | 5 | /** 6 | * Created by froger_mcs on 17/01/2017. 7 | */ 8 | @Builder 9 | @EqualsAndHashCode 10 | @ToString 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class Device { 14 | public Location location; 15 | } 16 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/InputType.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | 3 | /** 4 | * Created by froger_mcs on 17/01/2017. 5 | */ 6 | public enum InputType { 7 | UNSPECIFIC_INPUT_TYPE, 8 | KEYBOARD, 9 | TOUCH, 10 | VOICE 11 | } 12 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/Inputs.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | 3 | import lombok.*; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by froger_mcs on 17/01/2017. 9 | */ 10 | @Builder 11 | @EqualsAndHashCode 12 | @ToString 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class Inputs { 16 | public String intent; 17 | public List rawInputs; 18 | public List arguments; 19 | } 20 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/Location.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | import lombok.*; 3 | /** 4 | * Created by froger_mcs on 17/01/2017. 5 | * 6 | * https://developers.google.com/actions/reference/rest/Shared.Types/Location 7 | * 8 | */ 9 | @Builder 10 | @EqualsAndHashCode 11 | @ToString 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class Location { 15 | public Coordinates coordinates; 16 | public String formattedAddress; 17 | public String city; 18 | public String zipCode; 19 | public PostalAddress postalAddress; 20 | public String name; 21 | public String phoneNumber; 22 | public String notes; 23 | public String placeId; 24 | } 25 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/PostalAddress.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | 3 | import java.util.List; 4 | import lombok.*; 5 | 6 | /** 7 | * https://developers.google.com/actions/reference/rest/Shared.Types/PostalAddress 8 | */ 9 | @Builder 10 | @EqualsAndHashCode 11 | @ToString 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class PostalAddress { 15 | public int revision; 16 | public String regionCode; 17 | public String languageCode; 18 | public String postalCode; 19 | public String sortingCode; 20 | public String administrativeArea; 21 | public String locality; 22 | public String sublocality; 23 | public List addressLines; 24 | public List recipients; 25 | public String organization; 26 | } -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/RawInput.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | 3 | import lombok.*; 4 | 5 | /** 6 | * Created by froger_mcs on 17/01/2017. 7 | */ 8 | @Builder 9 | @EqualsAndHashCode 10 | @ToString 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class RawInput { 14 | public Time create_time; 15 | public String query; 16 | public InputType inputType; 17 | } 18 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/RootRequest.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | 3 | import lombok.*; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Created by froger_mcs on 17/01/2017. 9 | */ 10 | @Builder 11 | @EqualsAndHashCode 12 | @ToString 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class RootRequest { 16 | public User user; 17 | public Device device; 18 | public Conversation conversation; 19 | public List inputs; 20 | } 21 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/Time.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | 3 | import lombok.*; 4 | 5 | /** 6 | * Created by froger_mcs on 17/01/2017. 7 | */ 8 | @Builder 9 | @EqualsAndHashCode 10 | @ToString 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class Time { 14 | public int seconds; 15 | public int nanos; 16 | } 17 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/User.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | 3 | import lombok.*; 4 | 5 | import java.util.Date; 6 | import java.util.Locale; 7 | 8 | /** 9 | * Created by froger_mcs on 17/01/2017. 10 | */ 11 | 12 | @Builder 13 | @EqualsAndHashCode 14 | @ToString 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class User { 18 | public String userId; 19 | public UserProfile profile; 20 | public String access_token; 21 | public Locale locale; 22 | public Date lastSeen; 23 | } 24 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/request/UserProfile.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.request; 2 | 3 | import lombok.*; 4 | 5 | /** 6 | * Created by froger_mcs on 17/01/2017. 7 | */ 8 | @Builder 9 | @EqualsAndHashCode 10 | @ToString 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | public class UserProfile { 14 | public String givenName; 15 | public String familyName; 16 | public String displayName; 17 | public String email; 18 | } 19 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/response/AndroidApp.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.response; 2 | 3 | import lombok.*; 4 | 5 | import java.util.List; 6 | 7 | @Builder 8 | @EqualsAndHashCode 9 | @ToString 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class AndroidApp { 13 | public String packageName; 14 | public List versions; 15 | } 16 | -------------------------------------------------------------------------------- /gactions/src/main/java/com/frogermcs/gactions/api/response/BasicCard.java: -------------------------------------------------------------------------------- 1 | package com.frogermcs.gactions.api.response; 2 | 3 | import lombok.*; 4 | 5 | import java.util.List; 6 | 7 | @Builder 8 | @EqualsAndHashCode 9 | @ToString 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class BasicCard { 13 | public String title; 14 | public String subtitle; 15 | public String formattedText; 16 | public Image image; 17 | public List