From 0c939dfbd87f36e5c9fa22e3cf9a00a857f043aa Mon Sep 17 00:00:00 2001 From: Yuichi Araki Date: Thu, 24 Aug 2017 13:17:33 +0900 Subject: [PATCH 1/6] Add a sample for Cloud Conversation Engine Change-Id: I70f12c88bdd820ea510beadb3f8dbb98a9df789e --- conversation/Conversation/.gitignore | 10 + conversation/Conversation/README.md | 44 ++ conversation/Conversation/app/.gitignore | 1 + conversation/Conversation/app/build.gradle | 119 ++++ conversation/Conversation/app/lint.xml | 24 + .../Conversation/app/proguard-rules.pro | 23 + .../conversation/MainActivityTest.java | 107 ++++ .../cloud/android/conversation/TestUtils.java | 48 ++ .../api/ConversationServiceTest.java | 81 +++ .../conversation/api/UtteranceTest.java | 54 ++ .../ui/AudioIndicatorViewTest.java | 88 +++ .../conversation/ui/BubbleViewTest.java | 57 ++ .../conversation/ui/InputHelperTest.java | 126 ++++ .../ui/MessageDialogFragmentTest.java | 58 ++ .../cloud/android/conversation/ui/UiTest.java | 39 ++ .../app/src/debug/AndroidManifest.xml | 28 + .../conversation/ui/UiTestActivity.java | 67 +++ .../res/layout/activity_conversation_test.xml | 50 ++ .../app/src/main/AndroidManifest.xml | 44 ++ .../android/conversation/MainActivity.java | 222 +++++++ .../conversation/api/ConversationService.java | 543 ++++++++++++++++++ .../android/conversation/api/Utterance.java | 91 +++ .../conversation/ui/AudioIndicatorView.java | 109 ++++ .../android/conversation/ui/BubbleView.java | 92 +++ .../ui/ConversationHistoryAdapter.java | 94 +++ .../android/conversation/ui/InputHelper.java | 293 ++++++++++ .../ui/MessageDialogFragment.java | 78 +++ .../conversation/util/VoiceRecorder.java | 229 ++++++++ .../cloud/conversation/v1alpha/context.proto | 116 ++++ .../v1alpha/conversation_service.proto | 264 +++++++++ .../conversation/v1alpha/detect_intent.proto | 449 +++++++++++++++ .../conversation/v1alpha/entity_type.proto | 187 ++++++ .../cloud/conversation/v1alpha/intent.proto | 356 ++++++++++++ .../v1alpha/session_entity_type.proto | 92 +++ .../src/main/res/drawable/bubble_incoming.xml | 40 ++ .../src/main/res/drawable/bubble_outgoing.xml | 40 ++ .../app/src/main/res/drawable/ic_keyboard.xml | 26 + .../app/src/main/res/drawable/ic_mic.xml | 26 + .../app/src/main/res/drawable/ic_send.xml | 26 + .../app/src/main/res/layout/activity_main.xml | 83 +++ .../src/main/res/layout/item_conversation.xml | 37 ++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4379 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2690 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 6334 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 10587 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 15157 bytes .../app/src/main/res/values/attrs.xml | 26 + .../app/src/main/res/values/colors.xml | 26 + .../app/src/main/res/values/dimens.xml | 25 + .../app/src/main/res/values/strings.xml | 25 + .../app/src/main/res/values/styles.xml | 29 + .../app/src/main/res/xml/backup_scheme.xml | 22 + conversation/Conversation/build.gradle | 39 ++ conversation/Conversation/gradle.properties | 22 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + conversation/Conversation/gradlew | 160 ++++++ conversation/Conversation/gradlew.bat | 90 +++ conversation/Conversation/settings.gradle | 1 + 59 files changed, 5032 insertions(+) create mode 100644 conversation/Conversation/.gitignore create mode 100644 conversation/Conversation/README.md create mode 100644 conversation/Conversation/app/.gitignore create mode 100644 conversation/Conversation/app/build.gradle create mode 100644 conversation/Conversation/app/lint.xml create mode 100644 conversation/Conversation/app/proguard-rules.pro create mode 100644 conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/MainActivityTest.java create mode 100644 conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/TestUtils.java create mode 100644 conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/ConversationServiceTest.java create mode 100644 conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/UtteranceTest.java create mode 100644 conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/AudioIndicatorViewTest.java create mode 100644 conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/BubbleViewTest.java create mode 100644 conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/InputHelperTest.java create mode 100644 conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/MessageDialogFragmentTest.java create mode 100644 conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/UiTest.java create mode 100644 conversation/Conversation/app/src/debug/AndroidManifest.xml create mode 100644 conversation/Conversation/app/src/debug/java/com/google/cloud/android/conversation/ui/UiTestActivity.java create mode 100644 conversation/Conversation/app/src/debug/res/layout/activity_conversation_test.xml create mode 100644 conversation/Conversation/app/src/main/AndroidManifest.xml create mode 100644 conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/MainActivity.java create mode 100644 conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/ConversationService.java create mode 100644 conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/Utterance.java create mode 100644 conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/AudioIndicatorView.java create mode 100644 conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/BubbleView.java create mode 100644 conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/ConversationHistoryAdapter.java create mode 100644 conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/InputHelper.java create mode 100644 conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/MessageDialogFragment.java create mode 100644 conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/util/VoiceRecorder.java create mode 100644 conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/context.proto create mode 100644 conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/conversation_service.proto create mode 100644 conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/detect_intent.proto create mode 100644 conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/entity_type.proto create mode 100644 conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/intent.proto create mode 100644 conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/session_entity_type.proto create mode 100644 conversation/Conversation/app/src/main/res/drawable/bubble_incoming.xml create mode 100644 conversation/Conversation/app/src/main/res/drawable/bubble_outgoing.xml create mode 100644 conversation/Conversation/app/src/main/res/drawable/ic_keyboard.xml create mode 100644 conversation/Conversation/app/src/main/res/drawable/ic_mic.xml create mode 100644 conversation/Conversation/app/src/main/res/drawable/ic_send.xml create mode 100644 conversation/Conversation/app/src/main/res/layout/activity_main.xml create mode 100644 conversation/Conversation/app/src/main/res/layout/item_conversation.xml create mode 100644 conversation/Conversation/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 conversation/Conversation/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 conversation/Conversation/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 conversation/Conversation/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 conversation/Conversation/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 conversation/Conversation/app/src/main/res/values/attrs.xml create mode 100644 conversation/Conversation/app/src/main/res/values/colors.xml create mode 100644 conversation/Conversation/app/src/main/res/values/dimens.xml create mode 100644 conversation/Conversation/app/src/main/res/values/strings.xml create mode 100644 conversation/Conversation/app/src/main/res/values/styles.xml create mode 100644 conversation/Conversation/app/src/main/res/xml/backup_scheme.xml create mode 100644 conversation/Conversation/build.gradle create mode 100644 conversation/Conversation/gradle.properties create mode 100644 conversation/Conversation/gradle/wrapper/gradle-wrapper.jar create mode 100644 conversation/Conversation/gradle/wrapper/gradle-wrapper.properties create mode 100755 conversation/Conversation/gradlew create mode 100644 conversation/Conversation/gradlew.bat create mode 100644 conversation/Conversation/settings.gradle diff --git a/conversation/Conversation/.gitignore b/conversation/Conversation/.gitignore new file mode 100644 index 00000000..fe900bc7 --- /dev/null +++ b/conversation/Conversation/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild + +app/src/main/res/raw/*.json diff --git a/conversation/Conversation/README.md b/conversation/Conversation/README.md new file mode 100644 index 00000000..473c8d2f --- /dev/null +++ b/conversation/Conversation/README.md @@ -0,0 +1,44 @@ +# Google Cloud Conversation Engine examples + +This directory contains Android example that uses the +[Google Cloud Conversation Engine](https://cloud.google.com/conversation/). + +## Prerequisites + +### Enable Cloud Conversation Engine + +If you have not already done so, [enable Cloud Conversation Engine for your project]( +https://cloud.google.com/conversation/docs/quickstart). + +### Set Up to Authenticate With Your Project's Credentials + +This Android app uses JSON credential file locally stored in the resources. ***You should not do +this in your production app.*** Instead, you should set up your own backend server that +authenticates app users. The server should delegate API calls from your client app. This way, you +can enforce usage quota per user. Alternatively, you should get the access token on the server side, +and supply client app with it. The access token will expire in a short while. + +In this sample, we just put the Service Account in the client for ease of use. The app still gets +an access token using the service account credential, and use the token to call the API, so you can +see how to do so. + +In order to try out this sample, visit the [Cloud Console](https://console.cloud.google.com/), and +navigate to: +`API Manager > Credentials > Create credentials > Service account key > New service account`. +Create a new service account, and download the JSON credentials file. Put the file in the app +resources as `app/src/main/res/raw/credential.json`. + +Again, ***you should not do this in your production app.*** + +See the [Cloud Platform Auth Guide](https://cloud.google.com/docs/authentication#developer_workflow) +for more information. + +### Project name and agent name for API.AI + +Open the file `gradle.properties` and change the API.AI project name and agent name there. + +``` +apiAiProjectName=(your project name here) +apiAiAgentName(your agent name here) +apiAiLanguageCode=(language code, such as en-US) +``` diff --git a/conversation/Conversation/app/.gitignore b/conversation/Conversation/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/conversation/Conversation/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/conversation/Conversation/app/build.gradle b/conversation/Conversation/app/build.gradle new file mode 100644 index 00000000..bb233eab --- /dev/null +++ b/conversation/Conversation/app/build.gradle @@ -0,0 +1,119 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'com.android.application' +apply plugin: 'com.google.protobuf' + +ext { + supportLibraryVersion = '26.0.1' + grpcVersion = '1.5.0' +} + +android { + compileSdkVersion 26 + buildToolsVersion '26.0.1' + defaultConfig { + applicationId 'com.google.cloud.android.conversation' + minSdkVersion 16 + targetSdkVersion 26 + versionCode 1 + versionName '1.0' + testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' + vectorDrawables.useSupportLibrary = true + + // API.AI project & agent settings + buildConfigField 'String', 'PROJECT_NAME', "\"${project.property("apiAiProjectName")}\"" + buildConfigField 'String', 'AGENT_NAME', "\"${project.property("apiAiAgentName")}\"" + buildConfigField 'String', 'LANGUAGE_CODE', "\"${project.property("apiAiLanguageCode")}\"" + } + buildTypes { + debug { + minifyEnabled false + multiDexEnabled true + } + release { + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + configurations.all { + resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.2' + resolutionStrategy.force "com.android.support:support-annotations:$supportLibraryVersion" + } + lintOptions { + lintConfig file('lint.xml') + } +} + +protobuf { + protoc { + artifact = 'com.google.protobuf:protoc:3.3.0' + } + plugins { + javalite { + artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0' + } + grpc { + artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" + } + } + generateProtoTasks { + all().each { task -> + task.plugins { + javalite {} + grpc { + // Options added to --grpc_out + option 'lite' + } + } + } + } +} + +dependencies { + // Support Libraries + compile "com.android.support:appcompat-v7:$supportLibraryVersion" + compile "com.android.support:recyclerview-v7:$supportLibraryVersion" + + // gRPC + compile "io.grpc:grpc-okhttp:$grpcVersion" + compile "io.grpc:grpc-protobuf-lite:$grpcVersion" + compile "io.grpc:grpc-stub:$grpcVersion" + compile 'javax.annotation:javax.annotation-api:1.2' + protobuf 'com.google.protobuf:protobuf-java:3.3.1' + protobuf 'com.google.api.grpc:googleapis-common-protos:0.0.3' + + // OAuth2 for Google API + compile('com.google.auth:google-auth-library-oauth2-http:0.7.1') { + exclude module: 'httpclient' + } + + // Test + testCompile 'junit:junit:4.12' + testCompile 'org.mockito:mockito-core:2.9.0' + androidTestCompile 'org.mockito:mockito-android:2.9.0' + androidTestCompile 'com.android.support.test:runner:1.0.1' + androidTestCompile 'com.android.support.test.espresso:espresso-core:3.0.1' +} + +task copySecretKey(type: Copy) { + def File secretKey = file "$System.env.GOOGLE_APPLICATION_CREDENTIALS" + from secretKey.getParent() + include secretKey.getName() + into 'src/main/res/raw' + rename secretKey.getName(), "credential.json" +} +preBuild.dependsOn(copySecretKey) diff --git a/conversation/Conversation/app/lint.xml b/conversation/Conversation/app/lint.xml new file mode 100644 index 00000000..7022a9f8 --- /dev/null +++ b/conversation/Conversation/app/lint.xml @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/conversation/Conversation/app/proguard-rules.pro b/conversation/Conversation/app/proguard-rules.pro new file mode 100644 index 00000000..c8bcd1eb --- /dev/null +++ b/conversation/Conversation/app/proguard-rules.pro @@ -0,0 +1,23 @@ +# Copyright 2017 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Suppress warnings from gRPC dependencies +-dontwarn com.google.common.** +-dontwarn com.google.api.client.** +-dontwarn com.google.protobuf.** +-dontwarn io.grpc.** +-dontwarn okio.** +-dontwarn com.google.errorprone.annotations.** +-keep class io.grpc.internal.DnsNameResolveProvider +-keep class io.grpc.okhttp.OkHttpChannelProvider diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/MainActivityTest.java b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/MainActivityTest.java new file mode 100644 index 00000000..69fde9c3 --- /dev/null +++ b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/MainActivityTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.replaceText; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; + +import static com.google.cloud.android.conversation.TestUtils.hasDirection; + +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import android.Manifest; +import android.support.test.filters.LargeTest; +import android.support.test.filters.MediumTest; +import android.support.test.rule.ActivityTestRule; +import android.support.test.rule.GrantPermissionRule; +import android.support.test.runner.AndroidJUnit4; + +import com.google.cloud.android.conversation.api.ConversationService; +import com.google.cloud.android.conversation.api.Utterance; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + + +@RunWith(AndroidJUnit4.class) +public class MainActivityTest { + + @Rule + public ActivityTestRule activityRule = + new ActivityTestRule<>(MainActivity.class); + + @Rule + public GrantPermissionRule permissionRule = + GrantPermissionRule.grant(Manifest.permission.RECORD_AUDIO); + + private ConversationService.Listener mListener; + + @Before + public void setUp() { + final ConversationService service = activityRule.getActivity().mConversationService; + mListener = mock(ConversationService.Listener.class); + service.addListener(mListener); + } + + @After + public void tearDown() { + activityRule.getActivity().mConversationService.removeListener(mListener); + } + + @Test + @MediumTest + public void initialState() { + onView(withId(R.id.text)).check(matches(isDisplayed())); + onView(withId(R.id.toggle)).check(matches(isDisplayed())); + onView(withId(R.id.history)).check(matches(isDisplayed())); + } + + @Test + @LargeTest + public void detectIntentByText() { + verify(mListener, timeout(TestUtils.API_TIMEOUT_MILLIS)).onApiReady(); + onView(withId(R.id.text)).perform(replaceText("MainActivityTest")); + onView(withId(R.id.toggle)).perform(click()); + verify(mListener) + .onNewUtterance(eq(new Utterance(Utterance.OUTGOING, "MainActivityTest"))); + onView(withId(R.id.history)).check(matches(hasDescendant(withText("MainActivityTest")))); + verify(mListener, timeout(TestUtils.API_TIMEOUT_MILLIS)) + .onNewUtterance(argThat(hasDirection(Utterance.INCOMING))); + } + + @Test + @LargeTest + public void switchToVoice() { + verify(mListener, timeout(TestUtils.API_TIMEOUT_MILLIS)).onApiReady(); + onView(withId(R.id.toggle)).perform(click()); + onView(withId(R.id.indicator)).check(matches(isDisplayed())); + } + +} diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/TestUtils.java b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/TestUtils.java new file mode 100644 index 00000000..6e061af7 --- /dev/null +++ b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/TestUtils.java @@ -0,0 +1,48 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation; + +import com.google.cloud.android.conversation.api.Utterance; + +import org.mockito.ArgumentMatcher; + + +public class TestUtils { + + /** Timeout limit for API calls. */ + public static final int API_TIMEOUT_MILLIS = 10000; + + private TestUtils() { + } + + /** + * Matches an {@link Utterance} with the specified {@code direction}. + * + * @param direction The direction. Either {@link Utterance#INCOMING} or {@link + * Utterance#OUTGOING}. + * @return The matcher. + */ + public static ArgumentMatcher hasDirection(final int direction) { + return new ArgumentMatcher() { + @Override + public boolean matches(Utterance utterance) { + return utterance.direction == direction; + } + }; + } + +} diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/ConversationServiceTest.java b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/ConversationServiceTest.java new file mode 100644 index 00000000..fe76519d --- /dev/null +++ b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/ConversationServiceTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.api; + +import static com.google.cloud.android.conversation.TestUtils.hasDirection; + +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import android.content.Intent; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.rule.ServiceTestRule; +import android.support.test.runner.AndroidJUnit4; + +import com.google.cloud.android.conversation.TestUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.TimeoutException; + + +@RunWith(AndroidJUnit4.class) +public class ConversationServiceTest { + + @Rule + public final ServiceTestRule serviceRule = new ServiceTestRule(); + + private ConversationService mService; + private ConversationService.Listener mListener; + + @Before + public void setUp() throws TimeoutException { + mService = ConversationService.from(serviceRule.bindService( + new Intent(InstrumentationRegistry.getTargetContext(), ConversationService.class))); + mListener = mock(ConversationService.Listener.class); + mService.addListener(mListener); + } + + @After + public void tearDown() { + mService.removeListener(mListener); + serviceRule.unbindService(); + } + + @Test + @LargeTest + public void detectIntentByText() { + if (!mService.isApiReady()) { + verify(mListener, timeout(TestUtils.API_TIMEOUT_MILLIS)).onApiReady(); + } + mService.detectIntentByText("detectIntentByText"); + verify(mListener) + .onNewUtterance(eq(new Utterance(Utterance.OUTGOING, "detectIntentByText"))); + verify(mListener, timeout(TestUtils.API_TIMEOUT_MILLIS)) + .onNewUtterance(argThat(hasDirection(Utterance.INCOMING))); + } + + +} diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/UtteranceTest.java b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/UtteranceTest.java new file mode 100644 index 00000000..ba5fb1e2 --- /dev/null +++ b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/UtteranceTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.api; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + + +@RunWith(AndroidJUnit4.class) +public class UtteranceTest { + + @Test + public void instantiate() { + final Utterance utterance = new Utterance(Utterance.INCOMING, "Hello"); + assertThat(utterance.direction, is(Utterance.INCOMING)); + assertThat(utterance.text, is("Hello")); + } + + @Test + public void parcel() { + final Utterance original = new Utterance(Utterance.INCOMING, "Hello"); + final Parcel parcel = Parcel.obtain(); + try { + parcel.writeParcelable(original, 0); + parcel.setDataPosition(0); + final Utterance restored = parcel.readParcelable(getClass().getClassLoader()); + assertThat(restored.direction, is(Utterance.INCOMING)); + assertThat(restored.text, is("Hello")); + } finally { + parcel.recycle(); + } + } + +} diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/AudioIndicatorViewTest.java b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/AudioIndicatorViewTest.java new file mode 100644 index 00000000..0eb6f7c9 --- /dev/null +++ b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/AudioIndicatorViewTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.ui; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; +import static android.support.test.espresso.matcher.ViewMatchers.withId; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +import android.support.test.InstrumentationRegistry; +import android.support.test.espresso.UiController; +import android.support.test.espresso.ViewAction; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.View; + +import com.google.cloud.android.conversation.R; + +import org.hamcrest.Matcher; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + + +@RunWith(AndroidJUnit4.class) +public class AudioIndicatorViewTest extends UiTest { + + private AudioIndicatorView mAudioIndicatorView; + + @Before + public void setUp() throws Throwable { + mAudioIndicatorView = activityRule.getActivity().findViewById(R.id.indicator); + activityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + mAudioIndicatorView.setVisibility(View.VISIBLE); + } + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + @Test + @MediumTest + public void hearingVoice() throws Throwable { + assertThat(mAudioIndicatorView.isHearingVoice(), is(false)); + onView(withId(R.id.indicator)).perform(setHearingVoice(true)); + assertThat(mAudioIndicatorView.isHearingVoice(), is(true)); + onView(withId(R.id.indicator)).perform(setHearingVoice(false)); + assertThat(mAudioIndicatorView.isHearingVoice(), is(false)); + } + + private ViewAction setHearingVoice(final boolean hearingVoice) { + return new ViewAction() { + @Override + public Matcher getConstraints() { + return isAssignableFrom(AudioIndicatorView.class); + } + + @Override + public String getDescription() { + return "setHearingVoice"; + } + + @Override + public void perform(UiController uiController, View view) { + final AudioIndicatorView indicator = (AudioIndicatorView) view; + indicator.setHearingVoice(hearingVoice); + } + }; + } + +} diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/BubbleViewTest.java b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/BubbleViewTest.java new file mode 100644 index 00000000..8798e40e --- /dev/null +++ b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/BubbleViewTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.ui; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; + +import com.google.cloud.android.conversation.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + + +@RunWith(AndroidJUnit4.class) +public class BubbleViewTest extends UiTest { + + private BubbleView mBubbleView; + + @Before + public void setUp() { + mBubbleView = activityRule.getActivity().findViewById(R.id.bubble); + } + + @Test + @MediumTest + public void direction() throws Throwable { + assertThat(mBubbleView.getDirection(), is(BubbleView.DIRECTION_INCOMING)); + activityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + mBubbleView.setDirection(BubbleView.DIRECTION_OUTGOING); + } + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + assertThat(mBubbleView.getDirection(), is(BubbleView.DIRECTION_OUTGOING)); + } + +} diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/InputHelperTest.java b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/InputHelperTest.java new file mode 100644 index 00000000..14165977 --- /dev/null +++ b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/InputHelperTest.java @@ -0,0 +1,126 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.ui; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.action.ViewActions.replaceText; +import static android.support.test.espresso.matcher.ViewMatchers.withId; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageButton; + +import com.google.cloud.android.conversation.R; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + + +@RunWith(AndroidJUnit4.class) +public class InputHelperTest extends UiTest { + + private EditText mEditText; + private ImageButton mToggle; + private AudioIndicatorView mAudioIndicatorView; + + private InputHelper.Callback mCallback; + private InputHelper mInputHelper; + + @Before + public void setUp() throws Throwable { + final UiTestActivity activity = activityRule.getActivity(); + mEditText = activity.findViewById(R.id.input); + mToggle = activity.findViewById(R.id.toggle); + mAudioIndicatorView = activity.findViewById(R.id.indicator); + mCallback = mock(InputHelper.Callback.class); + activityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + mInputHelper = new InputHelper(mEditText, mToggle, mAudioIndicatorView, mCallback); + mInputHelper.setEnabled(true); + } + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } + + @After + public void tearDown() { + mInputHelper.release(); + } + + @Test + @MediumTest + public void initialState() { + assertNotNull(mInputHelper); + assertThat(mToggle.getContentDescription().toString(), is("Voice")); + assertThat(mEditText.isEnabled(), is(true)); + assertThat(mToggle.isEnabled(), is(true)); + assertThat(mToggle.getVisibility(), is(View.VISIBLE)); + assertThat(mAudioIndicatorView.getVisibility(), is(View.INVISIBLE)); + } + + @Test + @MediumTest + public void setEnabled() throws Throwable { + activityRule.runOnUiThread(new Runnable() { + @Override + public void run() { + mInputHelper.setEnabled(false); + } + }); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + assertThat(mEditText.isEnabled(), is(false)); + assertThat(mToggle.isEnabled(), is(false)); + } + + @Test + @MediumTest + public void type() { + onView(withId(R.id.input)).perform(replaceText("Hello")); + assertThat(mToggle.getContentDescription().toString(), is("Send")); + onView(withId(R.id.toggle)).perform(click()); + verify(mCallback).onText(eq("Hello")); + assertThat(mEditText.getText().toString(), is("")); + assertThat(mToggle.getContentDescription().toString(), is("Voice")); + } + + @Test + @MediumTest + public void audio() { + when(mCallback.ensureRecordAudioPermission()).thenReturn(true); + onView(withId(R.id.toggle)).perform(click()); + assertThat(mToggle.getContentDescription().toString(), is("Keyboard")); + assertThat(mAudioIndicatorView.getVisibility(), is(View.VISIBLE)); + onView(withId(R.id.toggle)).perform(click()); + assertThat(mToggle.getContentDescription().toString(), is("Voice")); + } + +} diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/MessageDialogFragmentTest.java b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/MessageDialogFragmentTest.java new file mode 100644 index 00000000..894684b8 --- /dev/null +++ b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/MessageDialogFragmentTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.ui; + +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.action.ViewActions.click; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; +import static android.support.test.espresso.matcher.ViewMatchers.withText; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + + +@RunWith(AndroidJUnit4.class) +public class MessageDialogFragmentTest extends UiTest { + + private MessageDialogFragment.Listener mListener; + private MessageDialogFragment mMessageDialogFragment; + + @Before + public void setUp() { + mListener = mock(MessageDialogFragment.Listener.class); + activityRule.getActivity().setMessageDialogFragmentListener(mListener); + mMessageDialogFragment = MessageDialogFragment.newInstance("MessageDialogFragmentTest"); + } + + @Test + @MediumTest + public void showAndDismiss() { + mMessageDialogFragment.show(activityRule.getActivity().getSupportFragmentManager(), "a"); + onView(withText("MessageDialogFragmentTest")).check(matches(isDisplayed())); + onView(withText(android.R.string.ok)).perform(click()); + verify(mListener).onMessageDialogDismissed(); + } + +} diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/UiTest.java b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/UiTest.java new file mode 100644 index 00000000..c05417e2 --- /dev/null +++ b/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/UiTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.ui; + +import android.Manifest; +import android.support.test.rule.ActivityTestRule; +import android.support.test.rule.GrantPermissionRule; + +import org.junit.Rule; + + +/** + * Base class for UI tests with {@link UiTestActivity}. + */ +abstract class UiTest { + + @Rule + public ActivityTestRule activityRule = + new ActivityTestRule<>(UiTestActivity.class); + + @Rule + public GrantPermissionRule permissionRule = + GrantPermissionRule.grant(Manifest.permission.RECORD_AUDIO); + +} diff --git a/conversation/Conversation/app/src/debug/AndroidManifest.xml b/conversation/Conversation/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..ef2bc625 --- /dev/null +++ b/conversation/Conversation/app/src/debug/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/conversation/Conversation/app/src/debug/java/com/google/cloud/android/conversation/ui/UiTestActivity.java b/conversation/Conversation/app/src/debug/java/com/google/cloud/android/conversation/ui/UiTestActivity.java new file mode 100644 index 00000000..dec2acb0 --- /dev/null +++ b/conversation/Conversation/app/src/debug/java/com/google/cloud/android/conversation/ui/UiTestActivity.java @@ -0,0 +1,67 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.ui; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.support.v7.app.AppCompatActivity; + +import com.google.cloud.android.conversation.R; + + +/** + * This is only for testing purpose. This activity has all UI components used in this app, and + * the testing code can use this to test UI components without actually calling APIs. + */ +public class UiTestActivity extends AppCompatActivity implements MessageDialogFragment.Listener { + + private static final int REQUEST_RECORD_AUDIO_PERMISSION = 1; + + private MessageDialogFragment.Listener mListener; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_conversation_test); + requestAudioPermission(); + } + + public void setMessageDialogFragmentListener(MessageDialogFragment.Listener listener) { + mListener = listener; + } + + private void requestAudioPermission() { + // The test code deals with the dialog. The permission is always granted. + if (ActivityCompat.checkSelfPermission(UiTestActivity.this, + Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(UiTestActivity.this, + new String[]{Manifest.permission.RECORD_AUDIO}, + REQUEST_RECORD_AUDIO_PERMISSION); + } + } + + @Override + public void onMessageDialogDismissed() { + if (mListener != null) { + mListener.onMessageDialogDismissed(); + } + } + +} diff --git a/conversation/Conversation/app/src/debug/res/layout/activity_conversation_test.xml b/conversation/Conversation/app/src/debug/res/layout/activity_conversation_test.xml new file mode 100644 index 00000000..ca666092 --- /dev/null +++ b/conversation/Conversation/app/src/debug/res/layout/activity_conversation_test.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + diff --git a/conversation/Conversation/app/src/main/AndroidManifest.xml b/conversation/Conversation/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..fd69963b --- /dev/null +++ b/conversation/Conversation/app/src/main/AndroidManifest.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/MainActivity.java b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/MainActivity.java new file mode 100644 index 00000000..3e48f422 --- /dev/null +++ b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/MainActivity.java @@ -0,0 +1,222 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation; + +import android.Manifest; +import android.content.ComponentName; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.IBinder; +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; +import android.support.v4.app.ActivityCompat; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.DefaultItemAnimator; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.widget.EditText; +import android.widget.ImageButton; + +import com.google.cloud.android.conversation.api.ConversationService; +import com.google.cloud.android.conversation.api.Utterance; +import com.google.cloud.android.conversation.ui.AudioIndicatorView; +import com.google.cloud.android.conversation.ui.ConversationHistoryAdapter; +import com.google.cloud.android.conversation.ui.InputHelper; +import com.google.cloud.android.conversation.ui.MessageDialogFragment; + + +public class MainActivity extends AppCompatActivity { + + private static final String FRAGMENT_MESSAGE_DIALOG = "message_dialog"; + private static final int REQUEST_RECORD_AUDIO_PERMISSION = 1; + + private static final String STATE_HISTORY = "history"; + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + ConversationService mConversationService; + + private LinearLayoutManager mLayoutManager; + private ConversationHistoryAdapter mAdapter; + + private InputHelper mInputHelper; + + private final ServiceConnection mServiceConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName componentName, IBinder binder) { + mConversationService = ConversationService.from(binder); + mConversationService.addListener(mSpeechServiceListener); + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { + mConversationService = null; + } + + }; + + private ConversationService.Listener mSpeechServiceListener + = new ConversationService.Listener() { + + @Override + public void onApiReady() { + mInputHelper.setEnabled(true); + } + + @Override + public void onNewUtterance(Utterance utterance) { + if (mInputHelper != null && utterance.direction == Utterance.OUTGOING) { + mInputHelper.showTranscript(null); + } + mAdapter.addUtterance(utterance); + mLayoutManager.scrollToPosition(mAdapter.getItemCount() - 1); + } + + @Override + public void onNewRecognition(String text) { + if (mInputHelper != null) { + mInputHelper.showTranscript(text); + } + } + + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + // The main conversation view + final RecyclerView history = findViewById(R.id.history); + history.setItemAnimator(new DefaultItemAnimator()); + mLayoutManager = new LinearLayoutManager(this); + mLayoutManager.setStackFromEnd(true); + history.setLayoutManager(mLayoutManager); + mAdapter = new ConversationHistoryAdapter(); + if (savedInstanceState != null) { + mAdapter.restoreHistory( + savedInstanceState.getParcelableArrayList(STATE_HISTORY)); + } + history.setAdapter(mAdapter); + // User input + mInputHelper = new InputHelper((EditText) findViewById(R.id.text), + (ImageButton) findViewById(R.id.toggle), + (AudioIndicatorView) findViewById(R.id.indicator), new InputHelper.Callback() { + @Override + public void onText(String text) { + if (mConversationService != null) { + mConversationService.detectIntentByText(text); + } + } + + @Override + public void onVoiceStart() { + if (mConversationService != null) { + mConversationService.startDetectIntentByVoice(mInputHelper.getSampleRate()); + } + } + + @Override + public void onVoice(byte[] data, int size) { + if (mConversationService != null) { + mConversationService.detectIntentByVoice(data, size); + } + } + + @Override + public void onVoiceEnd() { + if (mConversationService != null) { + mConversationService.finishDetectIntentByVoice(); + } + } + + @Override + public boolean ensureRecordAudioPermission() { + if (ActivityCompat.checkSelfPermission(MainActivity.this, + Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { + return true; + } else if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, + Manifest.permission.RECORD_AUDIO)) { + showPermissionMessageDialog(); + } else { + ActivityCompat.requestPermissions(MainActivity.this, + new String[]{Manifest.permission.RECORD_AUDIO}, + REQUEST_RECORD_AUDIO_PERMISSION); + } + return false; + } + }); + } + + @Override + protected void onDestroy() { + mInputHelper.release(); + super.onDestroy(); + } + + @Override + protected void onStart() { + super.onStart(); + + // Prepare Cloud Conversation Engine + bindService(new Intent(this, ConversationService.class), mServiceConnection, + BIND_AUTO_CREATE); + } + + @Override + protected void onStop() { + // Stop Cloud Conversation Engine + if (mConversationService != null) { + mConversationService.removeListener(mSpeechServiceListener); + unbindService(mServiceConnection); + mConversationService = null; + } + + super.onStop(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (mAdapter != null) { + outState.putParcelableArrayList(STATE_HISTORY, mAdapter.getHistory()); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + if (requestCode == REQUEST_RECORD_AUDIO_PERMISSION) { + if (permissions.length == 1 && grantResults.length == 1 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + mInputHelper.resumeAudio(); + } else { + mInputHelper.fallbackToText(); + } + } else { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + + private void showPermissionMessageDialog() { + MessageDialogFragment + .newInstance(getString(R.string.permission_message)) + .show(getSupportFragmentManager(), FRAGMENT_MESSAGE_DIALOG); + } + +} diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/ConversationService.java b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/ConversationService.java new file mode 100644 index 00000000..3e84485c --- /dev/null +++ b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/ConversationService.java @@ -0,0 +1,543 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.api; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; + +import com.google.auth.Credentials; +import com.google.auth.oauth2.AccessToken; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.android.conversation.BuildConfig; +import com.google.cloud.android.conversation.R; +import com.google.cloud.conversation.v1alpha.AudioEncoding; +import com.google.cloud.conversation.v1alpha.ConversationServiceGrpc; +import com.google.cloud.conversation.v1alpha.DetectIntentRequest; +import com.google.cloud.conversation.v1alpha.DetectIntentResponse; +import com.google.cloud.conversation.v1alpha.InputAudioConfig; +import com.google.cloud.conversation.v1alpha.QueryInput; +import com.google.cloud.conversation.v1alpha.QueryResult; +import com.google.cloud.conversation.v1alpha.StreamingDetectIntentRequest; +import com.google.cloud.conversation.v1alpha.StreamingDetectIntentResponse; +import com.google.cloud.conversation.v1alpha.StreamingInputAudioConfig; +import com.google.cloud.conversation.v1alpha.StreamingQueryInput; +import com.google.cloud.conversation.v1alpha.StreamingQueryParameters; +import com.google.cloud.conversation.v1alpha.StreamingRecognitionResult; +import com.google.cloud.conversation.v1alpha.TextInput; +import com.google.protobuf.ByteString; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ClientInterceptors; +import io.grpc.ManagedChannel; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import io.grpc.StatusException; +import io.grpc.StatusRuntimeException; +import io.grpc.internal.DnsNameResolverProvider; +import io.grpc.okhttp.OkHttpChannelProvider; +import io.grpc.stub.StreamObserver; + + +/** + * This service interacts with Cloud Conversation Engine. + */ +public class ConversationService extends Service { + + /** + * Callback listener for {@link ConversationService}. + */ + public interface Listener { + + /** + * Called when the API is ready. + */ + void onApiReady(); + + /** + * Called when a new {@link Utterance} occurred from either side of the conversation. + * + * @param utterance A new {@link Utterance}. + */ + void onNewUtterance(Utterance utterance); + + /** + * Called when a new piece of text is recognized in the speech recognition. + * + * @param text A new recognition result. + */ + void onNewRecognition(String text); + + } + + private static final String TAG = "ConversationService"; + + private static final String PREFS = "ConversationService"; + + private static final String PREF_ACCESS_TOKEN_VALUE = "access_token_value"; + private static final String PREF_ACCESS_TOKEN_EXPIRATION_TIME = "access_token_expiration_time"; + + /** We reuse an access token if its expiration time is longer than this. */ + private static final int ACCESS_TOKEN_EXPIRATION_TOLERANCE = 30 * 60 * 1000; // thirty minutes + /** We refresh the current access token before it expires. */ + private static final int ACCESS_TOKEN_FETCH_MARGIN = 60 * 1000; // one minute + + private static final List SCOPE = + Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"); + private static final String HOSTNAME = "conversation.googleapis.com"; + private static final int PORT = 443; + + /** The unique ID for this conversation; this should be changed as the session changes. */ + private static final int SESSION_ID = 1234567; + + private final ConversationBinder mBinder = new ConversationBinder(); + + private ConversationServiceGrpc.ConversationServiceStub mApi; + + private final ArrayList mListeners = new ArrayList<>(); + + private Handler mHandler; + + private final Runnable mFetchAccessTokenRunnable = new Runnable() { + @Override + public void run() { + prepareApi(); + } + }; + + private AccessTokenTask mAccessTokenTask; + + private final StreamObserver mTextResponseObserver + = new StreamObserver() { + + @Override + public void onNext(DetectIntentResponse detectIntentResponse) { + if (mHandler == null) { + return; + } + final String text = detectIntentResponse.getQueryResult().getFulfillment().getText(); + mHandler.post(new Runnable() { + @Override + public void run() { + dispatchNewUtterance(new Utterance(Utterance.INCOMING, text)); + } + }); + } + + @Override + public void onError(Throwable throwable) { + Log.e(TAG, "onError: ", throwable); + } + + @Override + public void onCompleted() { + Log.d(TAG, "onCompleted"); + } + + }; + + private StreamObserver mVoiceResponseObserver + = new StreamObserver() { + @Override + public void onNext(StreamingDetectIntentResponse response) { + if (mHandler == null) { + return; + } + final StreamingRecognitionResult recognitionResult = response.getRecognitionResult(); + if (recognitionResult != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (recognitionResult.getIsFinal()) { + dispatchNewUtterance(new Utterance(Utterance.OUTGOING, + recognitionResult.getTranscript())); + } else { + dispatchNewRecognition(recognitionResult.getTranscript()); + } + } + }); + } + final QueryResult queryResult = response.getQueryResult(); + if (queryResult != null && queryResult.hasIntent()) { + mHandler.post(new Runnable() { + @Override + public void run() { + dispatchNewUtterance(new Utterance(Utterance.INCOMING, + queryResult.getFulfillment().getText())); + } + }); + } + } + + @Override + public void onError(Throwable throwable) { + if (throwable instanceof StatusRuntimeException) { + if (((StatusRuntimeException) throwable).getStatus().getCode() == + Status.Code.NOT_FOUND) { + // Probably the audio didn't contain speech; ignore. + return; + } + } + Log.e(TAG, "Error while detecting intent by voice: ", throwable); + } + + @Override + public void onCompleted() { + Log.d(TAG, "Detect intent by voice completed."); + } + }; + + private StreamObserver mRequestObserver; + + public static ConversationService from(IBinder binder) { + return ((ConversationBinder) binder).getService(); + } + + @Override + public void onCreate() { + super.onCreate(); + prepareApi(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + releaseApi(); + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + public void addListener(@NonNull Listener listener) { + mListeners.add(listener); + if (mApi != null) { + listener.onApiReady(); + } + } + + public void removeListener(@NonNull Listener listener) { + mListeners.remove(listener); + } + + public boolean isApiReady() { + return mApi != null; + } + + public void detectIntentByText(String text) { + if (mApi == null) { + Log.w(TAG, "API not ready. Ignoring the request."); + return; + } + dispatchNewUtterance(new Utterance(Utterance.OUTGOING, text)); + mApi.detectIntent(DetectIntentRequest.newBuilder() + .setSession(createSessionName(BuildConfig.PROJECT_NAME, + BuildConfig.AGENT_NAME, SESSION_ID)) + .setQueryInput(QueryInput.newBuilder() + .setText(TextInput.newBuilder() + .setLanguageCode(BuildConfig.LANGUAGE_CODE) + .setText(text))) + .build(), mTextResponseObserver); + } + + public void startDetectIntentByVoice(int sampleRate) { + if (mApi == null) { + Log.w(TAG, "API not ready. Ignoring the request."); + return; + } + mRequestObserver = mApi.streamingDetectIntent(mVoiceResponseObserver); + mRequestObserver.onNext(StreamingDetectIntentRequest.newBuilder() + .setQueryParams(StreamingQueryParameters.newBuilder() + .setSession(createSessionName(BuildConfig.PROJECT_NAME, + BuildConfig.AGENT_NAME, SESSION_ID))) + .setQueryInput(StreamingQueryInput.newBuilder() + .setAudioConfig(StreamingInputAudioConfig.newBuilder() + .setConfig(InputAudioConfig.newBuilder() + .setAudioEncoding(AudioEncoding.AUDIO_ENCODING_LINEAR16) + .setLanguageCode(BuildConfig.LANGUAGE_CODE) + .setSampleRateHertz(sampleRate)))) + .build()); + } + + public void detectIntentByVoice(byte[] data, int size) { + if (mRequestObserver == null) { + return; + } + mRequestObserver.onNext(StreamingDetectIntentRequest.newBuilder() + .setInputAudio(ByteString.copyFrom(data, 0, size)) + .build()); + } + + public void finishDetectIntentByVoice() { + if (mRequestObserver == null) { + return; + } + mRequestObserver.onCompleted(); + mRequestObserver = null; + } + + private void prepareApi() { + if (mAccessTokenTask != null) { + return; + } + mHandler = new Handler(); + mAccessTokenTask = new AccessTokenTask(); + mAccessTokenTask.execute(); + } + + private void releaseApi() { + mHandler.removeCallbacks(mFetchAccessTokenRunnable); + mHandler = null; + // Release the gRPC channel. + if (mApi != null) { + final ManagedChannel channel = (ManagedChannel) mApi.getChannel(); + if (channel != null && !channel.isShutdown()) { + try { + channel.shutdown().awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Log.e(TAG, "Error shutting down the gRPC channel", e); + } + } + mApi = null; + } + } + + private void dispatchApiReady() { + for (int i = 0, size = mListeners.size(); i < size; i++) { + mListeners.get(i).onApiReady(); + } + } + + private void dispatchNewUtterance(Utterance utterance) { + for (int i = 0, size = mListeners.size(); i < size; i++) { + mListeners.get(i).onNewUtterance(utterance); + } + } + + private void dispatchNewRecognition(String text) { + for (int i = 0, size = mListeners.size(); i < size; i++) { + mListeners.get(i).onNewRecognition(text); + } + } + + private String createSessionName(String project, String agent, int id) { + return String.format(Locale.US, "projects/%s/agents/%s/sessions/%d", project, agent, id); + } + + private class ConversationBinder extends Binder { + + ConversationService getService() { + return ConversationService.this; + } + + } + + private class AccessTokenTask extends AsyncTask { + + @Override + protected AccessToken doInBackground(Void... voids) { + final SharedPreferences prefs = getApplication() + .getSharedPreferences(PREFS, Context.MODE_PRIVATE); + final String tokenValue = prefs.getString(PREF_ACCESS_TOKEN_VALUE, null); + long expirationTime = prefs.getLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, -1); + + // Check if the current token is still valid for a while + if (tokenValue != null && expirationTime > 0) { + if (expirationTime + > System.currentTimeMillis() + ACCESS_TOKEN_EXPIRATION_TOLERANCE) { + return new AccessToken(tokenValue, new Date(expirationTime)); + } + } + // ***** WARNING ***** + // In this sample, we load the credential from a JSON file stored in a raw resource + // folder of this client app. You should never do this in your app. Instead, store + // the file in your server and obtain an access token from there. + // ******************* + final InputStream stream = getApplication().getResources() + .openRawResource(R.raw.credential); + try { + final GoogleCredentials credentials = GoogleCredentials.fromStream(stream) + .createScoped(SCOPE); + final AccessToken token = credentials.refreshAccessToken(); + prefs.edit() + .putString(PREF_ACCESS_TOKEN_VALUE, token.getTokenValue()) + .putLong(PREF_ACCESS_TOKEN_EXPIRATION_TIME, + token.getExpirationTime().getTime()) + .apply(); + return token; + } catch (IOException e) { + Log.e(TAG, "Failed to obtain access token.", e); + } + return null; + } + + @Override + protected void onPostExecute(AccessToken accessToken) { + final ManagedChannel channel = new OkHttpChannelProvider() + .builderForAddress(HOSTNAME, PORT) + .nameResolverFactory(new DnsNameResolverProvider()) + .intercept(new GoogleCredentialsInterceptor(new GoogleCredentials(accessToken) + .createScoped(SCOPE))) + .build(); + mApi = ConversationServiceGrpc.newStub(channel); + dispatchApiReady(); + + // Schedule access token refresh before it expires + if (mHandler != null) { + mHandler.postDelayed(mFetchAccessTokenRunnable, + Math.max(accessToken.getExpirationTime().getTime() + - System.currentTimeMillis() - ACCESS_TOKEN_FETCH_MARGIN, + ACCESS_TOKEN_EXPIRATION_TOLERANCE)); + } + } + + } + + /** + * Authenticates the gRPC channel using the specified {@link GoogleCredentials}. + */ + private static class GoogleCredentialsInterceptor implements ClientInterceptor { + + private final Credentials mCredentials; + + private Metadata mCached; + + private Map> mLastMetadata; + + GoogleCredentialsInterceptor(Credentials credentials) { + mCredentials = credentials; + } + + @Override + public ClientCall interceptCall( + final MethodDescriptor method, CallOptions callOptions, + final Channel next) { + return new ClientInterceptors.CheckedForwardingClientCall( + next.newCall(method, callOptions)) { + @Override + protected void checkedStart(Listener responseListener, Metadata headers) + throws StatusException { + Metadata cachedSaved; + URI uri = serviceUri(next, method); + synchronized (this) { + Map> latestMetadata = getRequestMetadata(uri); + if (mLastMetadata == null || mLastMetadata != latestMetadata) { + mLastMetadata = latestMetadata; + mCached = toHeaders(mLastMetadata); + } + cachedSaved = mCached; + } + headers.merge(cachedSaved); + delegate().start(responseListener, headers); + } + }; + } + + /** + * Generate a JWT-specific service URI. The URI is simply an identifier with enough + * information for a service to know that the JWT was intended for it. The URI will + * commonly be verified with a simple string equality check. + */ + private URI serviceUri(Channel channel, MethodDescriptor method) + throws StatusException { + String authority = channel.authority(); + if (authority == null) { + throw Status.UNAUTHENTICATED + .withDescription("Channel has no authority") + .asException(); + } + // Always use HTTPS + final String scheme = "https"; + final int defaultPort = 443; + String path = "/" + MethodDescriptor.extractFullServiceName(method.getFullMethodName()); + URI uri; + try { + uri = new URI(scheme, authority, path, null, null); + } catch (URISyntaxException e) { + throw Status.UNAUTHENTICATED + .withDescription("Unable to construct service URI for auth") + .withCause(e).asException(); + } + // The default port must not be present. Alternative ports should be present. + if (uri.getPort() == defaultPort) { + uri = removePort(uri); + } + return uri; + } + + private URI removePort(URI uri) throws StatusException { + try { + return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), -1 /* port */, + uri.getPath(), uri.getQuery(), uri.getFragment()); + } catch (URISyntaxException e) { + throw Status.UNAUTHENTICATED + .withDescription("Unable to construct service URI after removing port") + .withCause(e).asException(); + } + } + + private Map> getRequestMetadata(URI uri) throws StatusException { + try { + return mCredentials.getRequestMetadata(uri); + } catch (IOException e) { + throw Status.UNAUTHENTICATED.withCause(e).asException(); + } + } + + private static Metadata toHeaders(Map> metadata) { + Metadata headers = new Metadata(); + if (metadata != null) { + for (String key : metadata.keySet()) { + Metadata.Key headerKey = Metadata.Key.of( + key, Metadata.ASCII_STRING_MARSHALLER); + for (String value : metadata.get(key)) { + headers.put(headerKey, value); + } + } + } + return headers; + } + + } + +} diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/Utterance.java b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/Utterance.java new file mode 100644 index 00000000..85c01c16 --- /dev/null +++ b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/Utterance.java @@ -0,0 +1,91 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.api; + +import android.os.Parcel; +import android.os.Parcelable; + +public class Utterance implements Parcelable { + + public static final int INCOMING = 1; + public static final int OUTGOING = 2; + + public final int direction; + public final String text; + + public Utterance(int direction, String text) { + this.direction = direction; + this.text = text; + } + + protected Utterance(Parcel in) { + direction = in.readInt(); + text = in.readString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Utterance utterance = (Utterance) o; + + //noinspection SimplifiableIfStatement + if (direction != utterance.direction) return false; + return text != null ? text.equals(utterance.text) : utterance.text == null; + + } + + @Override + public int hashCode() { + int result = direction; + result = 31 * result + (text != null ? text.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Utterance{" + + "direction=" + direction + + ", text='" + text + '\'' + + '}'; + } + + public static final Creator CREATOR = new Creator() { + @Override + public Utterance createFromParcel(Parcel in) { + return new Utterance(in); + } + + @Override + public Utterance[] newArray(int size) { + return new Utterance[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int i) { + parcel.writeInt(direction); + parcel.writeString(text); + } + +} diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/AudioIndicatorView.java b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/AudioIndicatorView.java new file mode 100644 index 00000000..d5560754 --- /dev/null +++ b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/AudioIndicatorView.java @@ -0,0 +1,109 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.ui; + +import android.animation.ObjectAnimator; +import android.content.Context; +import android.content.res.ColorStateList; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.support.v4.widget.ImageViewCompat; +import android.support.v7.widget.AppCompatImageView; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; + +import com.google.cloud.android.conversation.R; + + +/** + * Shows microphone icon indicating that the app is listening to audio. + */ +public class AudioIndicatorView extends AppCompatImageView { + + private final int mColorNormal; + private final int mColorHearingVoice; + + private boolean mHearingVoice; + private ObjectAnimator mAnimator; + + public AudioIndicatorView(Context context) { + this(context, null); + } + + public AudioIndicatorView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public AudioIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setImageResource(R.drawable.ic_mic); + mColorNormal = ContextCompat.getColor(context, R.color.input_button); + mColorHearingVoice = ContextCompat.getColor(context, R.color.accent); + ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(mColorNormal)); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + startAnimating(); + } + + @Override + protected void onDetachedFromWindow() { + if (mHearingVoice) { + stopAnimating(); + } + super.onDetachedFromWindow(); + } + + public void setHearingVoice(boolean hearingVoice) { + if (mHearingVoice == hearingVoice) { + return; + } + mHearingVoice = hearingVoice; + if (hearingVoice) { + stopAnimating(); + ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(mColorHearingVoice)); + } else { + startAnimating(); + ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(mColorNormal)); + } + } + + public boolean isHearingVoice() { + return mHearingVoice; + } + + private void startAnimating() { + mAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 1.f, 0.3f); + mAnimator.setRepeatCount(ObjectAnimator.INFINITE); + mAnimator.setRepeatMode(ObjectAnimator.REVERSE); + mAnimator.setDuration(1000); + mAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); + mAnimator.start(); + } + + private void stopAnimating() { + if (mAnimator != null) { + mAnimator.end(); + setAlpha(1.0f); + mAnimator = null; + } + } + +} diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/BubbleView.java b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/BubbleView.java new file mode 100644 index 00000000..3a4c9a6c --- /dev/null +++ b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/BubbleView.java @@ -0,0 +1,92 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.ui; + +import android.content.Context; +import android.content.res.ColorStateList; +import android.content.res.TypedArray; +import android.support.annotation.IntDef; +import android.support.annotation.Nullable; +import android.support.v4.content.ContextCompat; +import android.support.v4.view.ViewCompat; +import android.support.v7.widget.AppCompatTextView; +import android.util.AttributeSet; + +import com.google.cloud.android.conversation.R; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + + +/** + * A {@link android.widget.TextView} that displays text in a speech bubble. + */ +public class BubbleView extends AppCompatTextView { + + public static final int DIRECTION_INCOMING = 1; + public static final int DIRECTION_OUTGOING = 2; + + private ColorStateList mTintIncoming; + private ColorStateList mTintOutgoing; + + @IntDef({DIRECTION_INCOMING, DIRECTION_OUTGOING}) + @Retention(RetentionPolicy.SOURCE) + @interface Direction { + } + + private int mDirection; + + public BubbleView(Context context) { + this(context, null); + } + + public BubbleView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + @SuppressWarnings("WrongConstant") + public BubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mTintIncoming = ColorStateList.valueOf( + ContextCompat.getColor(context, R.color.incoming)); + mTintOutgoing = ColorStateList.valueOf( + ContextCompat.getColor(context, R.color.outgoing)); + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BubbleView); + setDirection(a.getInt(R.styleable.BubbleView_direction, DIRECTION_INCOMING)); + a.recycle(); + } + + public void setDirection(@Direction int direction) { + if (mDirection == direction) { + return; + } + mDirection = direction; + if (mDirection == DIRECTION_INCOMING) { + setBackgroundResource(R.drawable.bubble_incoming); + ViewCompat.setBackgroundTintList(this, mTintIncoming); + } else { + setBackgroundResource(R.drawable.bubble_outgoing); + ViewCompat.setBackgroundTintList(this, mTintOutgoing); + } + } + + @Direction + public int getDirection() { + return mDirection; + } + +} diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/ConversationHistoryAdapter.java b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/ConversationHistoryAdapter.java new file mode 100644 index 00000000..03f39f96 --- /dev/null +++ b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/ConversationHistoryAdapter.java @@ -0,0 +1,94 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.ui; + +import android.support.v7.widget.RecyclerView; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.FrameLayout; + +import com.google.cloud.android.conversation.R; +import com.google.cloud.android.conversation.api.Utterance; + +import java.util.ArrayList; + + +public class ConversationHistoryAdapter extends + RecyclerView.Adapter { + + private final ArrayList mHistory = new ArrayList<>(); + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ViewHolder(LayoutInflater.from(parent.getContext()), parent); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + final Utterance utterance = mHistory.get(position); + holder.bind(utterance.text, + utterance.direction == Utterance.INCOMING + ? BubbleView.DIRECTION_INCOMING + : BubbleView.DIRECTION_OUTGOING); + } + + @Override + public int getItemCount() { + return mHistory.size(); + } + + public void addUtterance(Utterance utterance) { + mHistory.add(utterance); + notifyItemInserted(mHistory.size() - 1); + } + + public ArrayList getHistory() { + return mHistory; + } + + public void restoreHistory(ArrayList history) { + mHistory.clear(); + mHistory.addAll(history); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + FrameLayout frame; + BubbleView bubble; + + ViewHolder(LayoutInflater inflater, ViewGroup parent) { + super(inflater.inflate(R.layout.item_conversation, parent, false)); + frame = itemView.findViewById(R.id.frame); + bubble = itemView.findViewById(R.id.bubble); + } + + void bind(String message, int direction) { + bubble.setText(message); + bubble.setDirection(direction); + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bubble.getLayoutParams(); + if (direction == BubbleView.DIRECTION_INCOMING) { + lp.gravity = Gravity.START | Gravity.CENTER_VERTICAL; + } else { + lp.gravity = Gravity.END | Gravity.CENTER_VERTICAL; + } + bubble.setLayoutParams(lp); + } + + } + +} diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/InputHelper.java b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/InputHelper.java new file mode 100644 index 00000000..d54dea38 --- /dev/null +++ b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/InputHelper.java @@ -0,0 +1,293 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.ui; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.TextView; + +import com.google.cloud.android.conversation.R; +import com.google.cloud.android.conversation.util.VoiceRecorder; + + +/** + * Helper class for managing 2 input modes; software keyboard and audio. + */ +public class InputHelper { + + /** Software keyboard mode; input is empty */ + private static final int MODE_TEXT_EMPTY = 1; + /** Software keyboard mode; user is typing */ + private static final int MODE_TEXT_TYPING = 2; + /** Audio input mode */ + private static final int MODE_AUDIO = 3; + + private final EditText mText; + private final ImageButton mToggle; + private final AudioIndicatorView mIndicator; + + private final VoiceRecorder mVoiceRecorder; + + private final Callback mCallback; + + private int mMode = MODE_TEXT_EMPTY; + + private final String mDescriptionVoice; + private final String mDescriptionSend; + private final String mDescriptionKeyboard; + + private boolean mEnabled; + + /** + * Instantiates a new instance of {@link InputHelper}. + * + * @param text The text input for the software keyboard. This is also used to show + * transcript of audio input. + * @param toggle The button to toggle the modes. + * @param callback The callback. + */ + public InputHelper(EditText text, ImageButton toggle, AudioIndicatorView indicator, + Callback callback) { + mText = text; + mToggle = toggle; + mIndicator = indicator; + mCallback = callback; + mVoiceRecorder = new VoiceRecorder(wrapVoiceCallback(callback)); + // Bind event handlers + text.addTextChangedListener(new TextWatcherAdapter() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (mMode == MODE_AUDIO) { + return; + } + changeTextMode(s); + } + }); + text.setOnEditorActionListener(new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + send(); + return true; + } + }); + toggle.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (mMode == MODE_TEXT_EMPTY) { + startAudio(); + } else if (mMode == MODE_TEXT_TYPING) { + send(); + } else if (mMode == MODE_AUDIO) { + startText(); + } + } + }); + // String resources + final Context context = mText.getContext(); + mDescriptionVoice = context.getString(R.string.description_voice); + mDescriptionSend = context.getString(R.string.description_send); + mDescriptionKeyboard = context.getString(R.string.description_keyboard); + // Initial view states + mText.setEnabled(false); + mToggle.setEnabled(false); + changeTextMode(text.getText()); + } + + public void setEnabled(boolean enabled) { + if (mEnabled == enabled) { + return; + } + mEnabled = enabled; + mText.setEnabled(enabled); + mToggle.setEnabled(enabled); + } + + /** + * Releases all the retained resources. + */ + public void release() { + mVoiceRecorder.stop(); + } + + /** + * @return The sample rate for the audio input. + */ + public int getSampleRate() { + return mVoiceRecorder.getSampleRate(); + } + + /** + * Resumes audio input. + */ + public void resumeAudio() { + mVoiceRecorder.start(); + } + + /** + * Falls back to text input mode. + */ + public void fallbackToText() { + startText(); + } + + /** + * Shows a real-time audio recognition result. This is only available when the user is using + * audio input. + * + * @param transcript The transcript. Pass {@code null} to clear. + */ + public void showTranscript(@Nullable String transcript) { + if (mMode != MODE_AUDIO) { + return; + } + mText.setText(transcript); + } + + private void startAudio() { + if (mCallback.ensureRecordAudioPermission()) { + mIndicator.setVisibility(View.VISIBLE); + mText.getText().clear(); + mText.setEnabled(false); + mText.setHint(R.string.hint_audio); + mToggle.setImageResource(R.drawable.ic_keyboard); + mToggle.setContentDescription(mDescriptionKeyboard); + mMode = MODE_AUDIO; + mVoiceRecorder.start(); + } + } + + private void startText() { + mVoiceRecorder.stop(); + mIndicator.setVisibility(View.INVISIBLE); + mText.getText().clear(); + mText.setEnabled(true); + mText.setHint(R.string.hint_text); + mToggle.setImageResource(R.drawable.ic_mic); + mToggle.setContentDescription(mDescriptionVoice); + mMode = MODE_TEXT_EMPTY; + } + + private void send() { + if (!mEnabled) { + return; + } + final Editable content = mText.getText(); + if (TextUtils.isEmpty(content)) { + return; + } + mCallback.onText(content.toString()); + content.clear(); + changeTextMode(null); + } + + private void changeTextMode(CharSequence s) { + if (TextUtils.isEmpty(s)) { + mToggle.setImageResource(R.drawable.ic_mic); + mToggle.setContentDescription(mDescriptionVoice); + mMode = MODE_TEXT_EMPTY; + } else { + mToggle.setImageResource(R.drawable.ic_send); + mToggle.setContentDescription(mDescriptionSend); + mMode = MODE_TEXT_TYPING; + } + } + + private VoiceRecorder.Callback wrapVoiceCallback(final Callback callback) { + return new VoiceRecorder.Callback() { + @Override + public void onVoiceStart() { + callback.onVoiceStart(); + mIndicator.post(new Runnable() { + @Override + public void run() { + mIndicator.setHearingVoice(true); + } + }); + } + + @Override + public void onVoice(byte[] data, int size) { + callback.onVoice(data, size); + } + + @Override + public void onVoiceEnd() { + mIndicator.post(new Runnable() { + @Override + public void run() { + mIndicator.setHearingVoice(false); + } + }); + callback.onVoiceEnd(); + } + }; + } + + /** + * Callbacks for {@link InputHelper}. + */ + public static abstract class Callback extends VoiceRecorder.Callback { + + /** + * Called when a new text is input from the keyboard. + * + * @param text A new text input. + */ + public void onText(String text) { + } + + /** + * Called when the {@link InputHelper} needs to make sure that the app has permission for + * audio recording. + * + * @return {@code true} if audio recording permission is available. If the implementation + * returns {@code false}, it is responsible for calling {@link #resumeAudio()} after the + * permission is granted. + */ + public boolean ensureRecordAudioPermission() { + return false; + } + + } + + /** + * A convenience class for overriding some methods of {@link TextWatcher}. + */ + private static abstract class TextWatcherAdapter implements TextWatcher { + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + } + + } + +} diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/MessageDialogFragment.java b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/MessageDialogFragment.java new file mode 100644 index 00000000..0cd15a1f --- /dev/null +++ b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/MessageDialogFragment.java @@ -0,0 +1,78 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.ui; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatDialogFragment; + + +/** + * A simple dialog with a message. + * + *

The calling {@link android.app.Activity} needs to implement {@link + * MessageDialogFragment.Listener}.

+ */ +public class MessageDialogFragment extends AppCompatDialogFragment { + + interface Listener { + /** + * Called when the dialog is dismissed. + */ + void onMessageDialogDismissed(); + } + + private static final String ARG_MESSAGE = "message"; + + /** + * Creates a new instance of {@link MessageDialogFragment}. + * + * @param message The message to be shown on the dialog. + * @return A newly created dialog fragment. + */ + public static MessageDialogFragment newInstance(String message) { + final MessageDialogFragment fragment = new MessageDialogFragment(); + final Bundle args = new Bundle(); + args.putString(ARG_MESSAGE, message); + fragment.setArguments(args); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + return new AlertDialog.Builder(getContext()) + .setMessage(getArguments().getString(ARG_MESSAGE)) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + ((Listener) getActivity()).onMessageDialogDismissed(); + } + }) + .setOnDismissListener(new DialogInterface.OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialogInterface) { + ((Listener) getActivity()).onMessageDialogDismissed(); + } + }) + .create(); + } + +} diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/util/VoiceRecorder.java b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/util/VoiceRecorder.java new file mode 100644 index 00000000..691b46fa --- /dev/null +++ b/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/util/VoiceRecorder.java @@ -0,0 +1,229 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.android.conversation.util; + +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaRecorder; +import android.support.annotation.NonNull; + + +/** + * Continuously records audio and notifies the {@link VoiceRecorder.Callback} when voice (or any + * sound) is heard. + * + *

The recorded audio format is always {@link AudioFormat#ENCODING_PCM_16BIT} and + * {@link AudioFormat#CHANNEL_IN_MONO}. This class will automatically pick the right sample rate + * for the device. Use {@link #getSampleRate()} to get the selected value.

+ */ +public class VoiceRecorder { + + private static final int[] SAMPLE_RATE_CANDIDATES = new int[]{16000, 11025, 22050, 44100}; + + private static final int CHANNEL = AudioFormat.CHANNEL_IN_MONO; + private static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT; + + private static final int AMPLITUDE_THRESHOLD = 1500; + private static final int SPEECH_TIMEOUT_MILLIS = 2000; + private static final int MAX_SPEECH_LENGTH_MILLIS = 30 * 1000; + + public static abstract class Callback { + + /** + * Called when the recorder starts hearing voice. + */ + public void onVoiceStart() { + } + + /** + * Called when the recorder is hearing voice. + * + * @param data The audio data in {@link AudioFormat#ENCODING_PCM_16BIT}. + * @param size The size of the actual data in {@code data}. + */ + public void onVoice(byte[] data, int size) { + } + + /** + * Called when the recorder stops hearing voice. + */ + public void onVoiceEnd() { + } + } + + private final Callback mCallback; + + private AudioRecord mAudioRecord; + + private Thread mThread; + + private byte[] mBuffer; + + private final Object mLock = new Object(); + + /** The timestamp of the last time that voice is heard. */ + private long mLastVoiceHeardMillis = Long.MAX_VALUE; + + /** The timestamp when the current voice is started. */ + private long mVoiceStartedMillis; + + public VoiceRecorder(@NonNull Callback callback) { + mCallback = callback; + } + + /** + * Starts recording audio. + * + *

The caller is responsible for calling {@link #stop()} later.

+ */ + public void start() { + // Stop recording if it is currently ongoing. + stop(); + // Try to create a new recording session. + mAudioRecord = createAudioRecord(); + if (mAudioRecord == null) { + throw new RuntimeException("Cannot instantiate VoiceRecorder"); + } + // Start recording. + mAudioRecord.startRecording(); + // Start processing the captured audio. + mThread = new Thread(new ProcessVoice()); + mThread.start(); + } + + /** + * Stops recording audio. + */ + public void stop() { + synchronized (mLock) { + dismiss(); + if (mThread != null) { + mThread.interrupt(); + mThread = null; + } + if (mAudioRecord != null) { + mAudioRecord.stop(); + mAudioRecord.release(); + mAudioRecord = null; + } + mBuffer = null; + } + } + + /** + * Dismisses the currently ongoing utterance. + */ + public void dismiss() { + if (mLastVoiceHeardMillis != Long.MAX_VALUE) { + mLastVoiceHeardMillis = Long.MAX_VALUE; + mCallback.onVoiceEnd(); + } + } + + /** + * Retrieves the sample rate currently used to record audio. + * + * @return The sample rate of recorded audio. + */ + public int getSampleRate() { + if (mAudioRecord != null) { + return mAudioRecord.getSampleRate(); + } + return 0; + } + + /** + * Creates a new {@link AudioRecord}. + * + * @return A newly created {@link AudioRecord}, or null if it cannot be created (missing + * permissions?). + */ + private AudioRecord createAudioRecord() { + for (int sampleRate : SAMPLE_RATE_CANDIDATES) { + final int sizeInBytes = AudioRecord.getMinBufferSize(sampleRate, CHANNEL, ENCODING); + if (sizeInBytes == AudioRecord.ERROR_BAD_VALUE) { + continue; + } + final AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, + sampleRate, CHANNEL, ENCODING, sizeInBytes); + if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) { + mBuffer = new byte[sizeInBytes]; + return audioRecord; + } else { + audioRecord.release(); + } + } + return null; + } + + /** + * Continuously processes the captured audio and notifies {@link #mCallback} of corresponding + * events. + */ + private class ProcessVoice implements Runnable { + + @Override + public void run() { + while (true) { + synchronized (mLock) { + if (Thread.currentThread().isInterrupted()) { + break; + } + final int size = mAudioRecord.read(mBuffer, 0, mBuffer.length); + final long now = System.currentTimeMillis(); + if (isHearingVoice(mBuffer, size)) { + if (mLastVoiceHeardMillis == Long.MAX_VALUE) { + mVoiceStartedMillis = now; + mCallback.onVoiceStart(); + } + mCallback.onVoice(mBuffer, size); + mLastVoiceHeardMillis = now; + if (now - mVoiceStartedMillis > MAX_SPEECH_LENGTH_MILLIS) { + end(); + } + } else if (mLastVoiceHeardMillis != Long.MAX_VALUE) { + mCallback.onVoice(mBuffer, size); + if (now - mLastVoiceHeardMillis > SPEECH_TIMEOUT_MILLIS) { + end(); + } + } + } + } + } + + private void end() { + mLastVoiceHeardMillis = Long.MAX_VALUE; + mCallback.onVoiceEnd(); + } + + private boolean isHearingVoice(byte[] buffer, int size) { + for (int i = 0; i < size - 1; i += 2) { + // The buffer has LINEAR16 in little endian. + int s = buffer[i + 1]; + if (s < 0) s *= -1; + s <<= 8; + s += Math.abs(buffer[i]); + if (s > AMPLITUDE_THRESHOLD) { + return true; + } + } + return false; + } + + } + +} diff --git a/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/context.proto b/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/context.proto new file mode 100644 index 00000000..948cd4a8 --- /dev/null +++ b/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/context.proto @@ -0,0 +1,116 @@ +// Copyright 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.conversation.v1alpha; + +import "google/api/annotations.proto"; +import "google/protobuf/struct.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/cloud/conversation/v1alpha;conversation"; +option java_multiple_files = true; +option java_outer_classname = "ContextProto"; +option java_package = "com.google.cloud.conversation.v1alpha"; + +// Represents the current conditions of a user query. +message Context { + // *Required* The unique identifier of the context. + // Format: `projects//agents//sessions//contexts/`. + string name = 1; + + // *Optional* The number of conversational query requests after which the + // context expires. If set to `0` (the default) the context expires when an + // intent is detected for a query. Contexts expire automatically after + // 10 minutes even if there are no matching queries. + // If a context is added to a `DetectIntent` query + // (`QueryParameters.contexts`) + // and the `lifespan_count` for the context is less than or equal to `1`, + // the context can expire immediately if the `DetectIntent` query matches + // an intent. + int32 lifespan_count = 2; + + // *Optional* The collection of parameters associated with this context. + google.protobuf.Struct parameters = 3; +} + +// A request to list contexts that are active in the specified session. +message ListContextsRequest { + // *Required* The name of the session that contains the active contexts. + // Format: `projects//agents//sessions/`. + string parent = 1; +} + +// The message returned from the ListContexts method. +message ListContextsResponse { + // The list of contexts in the specified session. + repeated Context contexts = 1; +} + +// The request to retrieve the specified context. +message GetContextRequest { + // *Required* The name of the context to retrieve. + // Format: `projects//agents//sessions//contexts/`. + string name = 1; +} + +// The request to create a new context in the specified session. +message CreateContextRequest { + // *Required* The name of the session to create a new context in. + // Format: `projects//agents//sessions/`. + string parent = 1; + + // *Required* The context to create. + Context context = 2; +} + +// The request to update the specified context. +message UpdateContextRequest { + // *Required* The context to update. + Context context = 1; +} + +// The request to delete the specified context. +message DeleteContextRequest { + // *Required* The name of the context to delete. + // Format: "projects//agents//sessions//contexts/". + string name = 1; +} + +// The request to create multiple contexts in the specified session. +message BatchCreateContextsRequest { + // *Required* The name of the session to create the new contexts in. + // Format: `projects//agents//sessions/`. + string parent = 1; + + // *Required* The collection of contexts to create. + repeated Context contexts = 2; +} + +// The message returned from the BatchCreateContexts method. +message BatchCreateContextsResponse { + // The collection of created contexts. + repeated Context contexts = 1; +} + +// The request to delete all contexts in the specified session. +message DeleteAllContextsRequest { + // *Required* The name of the session to delete all contexts from. + // Format: `projects//agents//sessions/`. + string parent = 1; +} diff --git a/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/conversation_service.proto b/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/conversation_service.proto new file mode 100644 index 00000000..26bc643f --- /dev/null +++ b/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/conversation_service.proto @@ -0,0 +1,264 @@ +// Copyright 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.conversation.v1alpha; + +import "google/api/annotations.proto"; +import "google/cloud/conversation/v1alpha/context.proto"; +import "google/cloud/conversation/v1alpha/detect_intent.proto"; +import "google/cloud/conversation/v1alpha/entity_type.proto"; +import "google/cloud/conversation/v1alpha/intent.proto"; +import "google/cloud/conversation/v1alpha/session_entity_type.proto"; +import "google/protobuf/empty.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/cloud/conversation/v1alpha;conversation"; +option java_multiple_files = true; +option java_outer_classname = "ConversationServiceProto"; +option java_package = "com.google.cloud.conversation.v1alpha"; + +// # This service defines the Cloud Conversation Engine API. +// +// Conversation API +service ConversationService { + // Processes a natural language query and returns structured, actionable data + // as a result. This method is not idempotent, because it may cause contexts + // and session entity types to be updated, which in turn might affect + // results of future queries. + rpc DetectIntent(DetectIntentRequest) returns (DetectIntentResponse) { + option (google.api.http) = { + post: "/v1alpha/{session=projects/*/agents/*/sessions/*}:detectIntent" + body: "*" + }; + } + + // Processes a natural language query in audio format in a streaming fashion + // and returns structured, actionable data as a result. This method is only + // available via the gRPC API (not REST). + rpc StreamingDetectIntent(stream StreamingDetectIntentRequest) + returns (stream StreamingDetectIntentResponse); + + // Lists all entity types in the specified agent. + rpc ListEntityTypes(ListEntityTypesRequest) + returns (ListEntityTypesResponse) { + option (google.api.http) = { + get: "/v1alpha/{parent=projects/*/agents/*}/entityTypes" + }; + } + + // Retrieves the specified entity type. + rpc GetEntityType(GetEntityTypeRequest) returns (EntityType) { + option (google.api.http) = { + get: "/v1alpha/{name=projects/*/agents/*/entityTypes/*}" + }; + } + + // Creates a new entity type in the specified agent. + rpc CreateEntityType(CreateEntityTypeRequest) returns (EntityType) { + option (google.api.http) = { + post: "/v1alpha/{parent=projects/*/agents/*}/entityTypes" + body: "entity_type" + }; + } + + // Updates the specified entity type. + rpc UpdateEntityType(UpdateEntityTypeRequest) returns (EntityType) { + option (google.api.http) = { + put: "/v1alpha/{entity_type.name=projects/*/agents/*/entityTypes/*}" + body: "entity_type" + }; + } + + // Deletes the specified entity type. + rpc DeleteEntityType(DeleteEntityTypeRequest) + returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1alpha/{name=projects/*/agents/*/entityTypes/*}" + }; + } + + // Updates or creates multiple entity types in the specified agent. + rpc BatchUpdateEntityTypes(BatchUpdateEntityTypesRequest) + returns (BatchUpdateEntityTypesResponse) { + option (google.api.http) = { + post: "/v1alpha/{parent=projects/*/agents/*}/entityTypes:batchUpdate" + body: "*" + }; + } + + // Creates multiple new entities in the specified entity type (extends the + // existing collection of entries). + rpc BatchCreateEntities(BatchCreateEntitiesRequest) + returns (google.protobuf.Empty) { + option (google.api.http) = { + post: + "/v1alpha/{parent=projects/*/agents/*/entityTypes/*}/" + "entities:batchCreate" + body: "*" + }; + } + + // Updates entities in the specified entity type (replaces the existing + // collection of entries). + rpc BatchUpdateEntities(BatchUpdateEntitiesRequest) + returns (google.protobuf.Empty) { + option (google.api.http) = { + post: + "/v1alpha/{parent=projects/*/agents/*/entityTypes/*}/" + "entities:batchUpdate" + body: "*" + }; + } + + // Deletes entities in the specified entity type. + rpc BatchDeleteEntities(BatchDeleteEntitiesRequest) + returns (google.protobuf.Empty) { + option (google.api.http) = { + post: + "/v1alpha/{parent=projects/*/agents/*/entityTypes/*}/" + "entities:batchDelete" + body: "*" + }; + } + + // Lists all intents in the specified agent. + rpc ListIntents(ListIntentsRequest) returns (ListIntentsResponse) { + option (google.api.http) = { + get: "/v1alpha/{parent=projects/*/agents/*}/intents" + }; + } + + // Retrieves the specified intent. + rpc GetIntent(GetIntentRequest) returns (Intent) { + option (google.api.http) = { + get: "/v1alpha/{name=projects/*/agents/*/intents/*}" + }; + } + + // Creates a new intent in the specified agent. + rpc CreateIntent(CreateIntentRequest) returns (Intent) { + option (google.api.http) = { + post: "/v1alpha/{parent=projects/*/agents/*}/intents" + body: "intent" + }; + } + + // Updates the specified intent. + rpc UpdateIntent(UpdateIntentRequest) returns (Intent) { + option (google.api.http) = { + put: "/v1alpha/{intent.name=projects/*/agents/*/intents/*}" + body: "intent" + }; + } + + // Deletes the specified intent. + rpc DeleteIntent(DeleteIntentRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1alpha/{name=projects/*/agents/*/intents/*}" + }; + } + + // Retrieves the specified session entity type. + rpc GetSessionEntityType(GetSessionEntityTypeRequest) + returns (SessionEntityType) { + option (google.api.http) = { + get: "/v1alpha/{name=projects/*/agents/*/sessions/*/entityTypes/*}" + }; + } + + // Creates a new session entity type in the specified session. + rpc CreateSessionEntityType(CreateSessionEntityTypeRequest) + returns (SessionEntityType) { + option (google.api.http) = { + post: "/v1alpha/{parent=projects/*/agents/*/sessions/*}/entityTypes" + body: "session_entity_type" + }; + } + + // Updates the specified session entity type. + rpc UpdateSessionEntityType(UpdateSessionEntityTypeRequest) + returns (SessionEntityType) { + option (google.api.http) = { + put: "/v1alpha/{session_entity_type.name=projects/*/agents/*/sessions/*/" + "entityTypes/*}" + body: "session_entity_type" + }; + } + + // Deletes the specified session entity type. + rpc DeleteSessionEntityType(DeleteSessionEntityTypeRequest) + returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1alpha/{name=projects/*/agents/*/sessions/*/entityTypes/*}" + }; + } + + // Lists contexts which are active in the specified session. + rpc ListContexts(ListContextsRequest) returns (ListContextsResponse) { + option (google.api.http) = { + get: "/v1alpha/{parent=projects/*/agents/*/sessions/*}/contexts" + }; + } + + // Retrieves the specified context. + rpc GetContext(GetContextRequest) returns (Context) { + option (google.api.http) = { + get: "/v1alpha/{name=projects/*/agents/*/sessions/*/contexts/*}" + }; + } + + // Creates a new context in the specified session. + rpc CreateContext(CreateContextRequest) returns (Context) { + option (google.api.http) = { + post: "/v1alpha/{parent=projects/*/agents/*/sessions/*}/contexts" + body: "context" + }; + } + + // Updates the specified context. + rpc UpdateContext(UpdateContextRequest) returns (Context) { + option (google.api.http) = { + put: "/v1alpha/{context.name=projects/*/agents/*/sessions/*/contexts/*}" + body: "context" + }; + } + + // Deletes the specified context. + rpc DeleteContext(DeleteContextRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1alpha/{name=projects/*/agents/*/sessions/*/contexts/*}" + }; + } + + // Creates multiple new contexts in the specified session. + rpc BatchCreateContexts(BatchCreateContextsRequest) + returns (BatchCreateContextsResponse) { + option (google.api.http) = { + post: + "/v1alpha/{parent=projects/*/agents/*/sessions/*}/" + "contexts:batchCreate" + body: "*" + }; + } + + // Deletes all active context in the specified session. + rpc DeleteAllContexts(DeleteAllContextsRequest) + returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v1alpha/{parent=projects/*/agents/*/sessions/*}/contexts" + }; + } +} diff --git a/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/detect_intent.proto b/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/detect_intent.proto new file mode 100644 index 00000000..7cb93400 --- /dev/null +++ b/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/detect_intent.proto @@ -0,0 +1,449 @@ +// Copyright 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.conversation.v1alpha; + +import "google/api/annotations.proto"; +import "google/cloud/conversation/v1alpha/context.proto"; +import "google/cloud/conversation/v1alpha/intent.proto"; +import "google/cloud/conversation/v1alpha/session_entity_type.proto"; +import "google/protobuf/struct.proto"; +import "google/rpc/status.proto"; +import "google/type/latlng.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/cloud/conversation/v1alpha;conversation"; +option java_multiple_files = true; +option java_outer_classname = "DetectIntentProto"; +option java_package = "com.google.cloud.conversation.v1alpha"; + +// The request to detect user's intent. +message DetectIntentRequest { + // *Required* The name of the session this query is sent to. + // Format of the session name: + // `projects//agents//sessions/`. + // It’s up to the API caller to choose an appropriate session ID. It can be + // a random number or some type of user identifier (preferably hashed). + // The length of the session ID must not exceed 36 characters. + string session = 1; + + // *Optional* The parameters of this query. + QueryParameters query_params = 2; + + // *Required* The input specification. It can be set to: + // 1. an audio config + // which instructs the speech recognizer how to process the speech audio, + // 2. a conversational query in the form of text, or + // 3. an event that specifies which intent to trigger. + QueryInput query_input = 3; + + // *Optional* The natural language speech audio to be processed. This field + // should be populated iff `query_input` is set to an input audio config. + // A single request can contain up to 1 minute of speech audio data. + bytes input_audio = 5; +} + +// The message returned from the DetectIntent method. +message DetectIntentResponse { + // The unique identifier of the response. It can be used to + // locate a response in the training example set or for reporting issues. + string response_id = 1; + + // The results of the conversational query or event processing. + QueryResult query_result = 2; + + // Specifies the status of the webhook request. `webhook_status` + // is never populated in webhook requests. + google.rpc.Status webhook_status = 3; +} + +// Represents the parameters of the conversational query. +message QueryParameters { + // *Optional* The time zone of this conversational query from the + // [time zone database](https://www.iana.org/time-zones), e.g., + // America/New_York, Europe/Paris. If not provided, the time zone specified in + // agent settings is used. + string time_zone = 1; + + // *Optional* The geo location of this conversational query. + google.type.LatLng geo_location = 2; + + // *Optional* The collection of contexts to be activated before this query is + // executed. + repeated Context contexts = 4; + + // *Optional* Specifies whether to delete all contexts in the current session + // before the new ones are activated. + bool reset_contexts = 5; + + // *Optional* The collection of session entity types to replace or extend + // developer entities with for this query only. + repeated SessionEntityType session_entity_types = 6; + + // *Optional* This field can be used to pass custom data into the webhook + // associated with the agent. Arbitrary JSON objects are supported. + google.protobuf.Struct payload = 7; +} + +// Represents the query input. It can contain either: +// 1. an audio config which +// instructs the speech recognizer how to process the speech audio, +// 2. a conversational query in the form of text, or +// 3. an event that specifies which intent to trigger. +message QueryInput { + // *Required* The input specification. + oneof input { + // Instructs the speech recognizer how to process the speech audio. + InputAudioConfig audio_config = 1; + + // The natural language text to be processed. + TextInput text = 2; + + // The event to be processed. + EventInput event = 3; + } +} + +// Represents the result of conversational query or event processing. +message QueryResult { + // Contains the set of rich responses to present to the user. + message Fulfillment { + // The text to be pronounced to the user or shown on the + // screen. + string text = 1; + + // The collection of rich messages to present to the + // user. + repeated Intent.Result.Message messages = 2; + } + + // The original conversational query text: + // - If natural language text was provided as input, `query_text` contains + // a copy of the input. + // - If natural language speech audio was provided as input, `query_text` + // contains the speech recognition result. If speech recognizer produced + // multiple alternatives, a particular one is picked. + // - If an event was provided as input, `query_text` is not set. + string query_text = 1; + + // The confidence estimate between 0.0 and 1.0. A higher number + // indicates an estimated greater likelihood that the recognized words are + // correct. The default of 0.0 is a sentinel value indicating that confidence + // was not set. This field is populated if natural speech audio was provided + // as input. + float speech_recognition_confidence = 2; + + // The action name from the matched intent. + string action = 3; + + // The collection of extracted parameters. + google.protobuf.Struct parameters = 4; + + // This field is set to: + // - `false` if the matched intent has required parameters and not all of + // the required parameter values have been collected. + // - `true` if all required parameter values have been collected, or if the + // matched intent doesn't contain any required parameters. + bool all_required_params_collected = 5; + + // The responses to present to the user. + Fulfillment fulfillment = 6; + + // The collection of output contexts. If applicable, + // `output_contexts.parameters` contains entries with name + // `.original` containing the original parameter values + // before the query. + repeated Context output_contexts = 7; + + // The intent that matched the conversational query. Some, not + // all fields are filled in this message, including but not limited to: + // `name`, `display_name` and `webhook_state`. + Intent intent = 8; + + // The intent detection confidence. Values range from 0.0 + // (completely uncertain) to 1.0 (completely certain). + float intent_detection_confidence = 9; + + // Indicates whether a fallback intent was triggered. + bool fallback_intent_triggered = 10; + + // The free-form diagnostic info. For example, this field + // could contain webhook call latency. + google.protobuf.Struct diagnostic_info = 11; +} + +// The top-level message sent by the client to the +// `StreamingDetectIntent` method. +// +// Multiple request messages should be sent in order: +// +// 1. The first message must contain `query_params` and `query_input`. +// The message must not contain +// `input_audio`. +// +// 2. If `query_input` was set to a streaming input audio config, +// all subsequent messages must contain only `input_audio`. +// Otherwise, finish the request stream. +message StreamingDetectIntentRequest { + // *Required* The parameters of this query. + StreamingQueryParameters query_params = 1; + + // *Required* The input specification. It can be set to: + // + // 1. an audio config which instructs the speech recognizer how to process + // the speech audio, + // + // 2. a conversational query in the form of text, or + // + // 3. an event that specifies which intent to trigger. + StreamingQueryInput query_input = 2; + + // *Optional* The input audio content to be recognized. Must be sent if + // `query_input` was set to a streaming input audio config. The complete audio + // over all streaming messages must not exceed 1 minute. + bytes input_audio = 4; +} + +// The top-level message returned from the +// `StreamingDetectIntent` method. +// +// Multiple response messages can be returned in order: +// +// 1. If the input was set to streaming audio, the first one or more messages +// contain `recognition_result`. Each `recognition_result` represents a more +// complete transcript of what the user said. The last `recognition_result` +// has `is_final` set to `true`. +// +// 2. The next message contains `response_id`, `query_result` +// and optionally `webhook_status` if a WebHook was called. +message StreamingDetectIntentResponse { + // The unique identifier of the response. It can be used to + // locate a response in the training example set or for reporting issues. + string response_id = 1; + + // The result of speech recognition. + StreamingRecognitionResult recognition_result = 2; + + // The result of the conversational query or event processing. + QueryResult query_result = 3; + + // Specifies the status of the webhook request. + google.rpc.Status webhook_status = 4; +} + +// Represents the parameters of a streaming conversational query. +message StreamingQueryParameters { + // *Required* The name of the session the query is sent to. + // Format of the session name: `agents//sessions/`. + // It’s up to the API caller to choose an appropriate . It can be + // a random number or some type of user identifier (preferably hashed). + // The length of the session ID must not exceed 36 characters. + string session = 1; + + // *Optional* Other parameters. + QueryParameters params = 2; +} + +// Represents the streaming query input. It can contain either: +// +// 1. an audio config which instructs the speech recognizer how to process +// the speech audio, +// +// 2. a conversational query in the form of text, or +// +// 3. an event that specifies which intent to trigger. +message StreamingQueryInput { + // *Required* The input specification. + oneof input { + // Instructs the speech recognizer how to process the streamed speech audio. + StreamingInputAudioConfig audio_config = 1; + + // The natural language text to be processed. + TextInput text = 2; + + // The event to be processed. + EventInput event = 3; + } +} + +// Contains a speech recognition result corresponding to a portion of the audio +// that is currently being processed or an indication that this is the end +// of the single requested utterance. +// +// Example: +// +// 1. transcript: "tube" +// +// 2. transcript: "to be a" +// +// 3. transcript: "to be" +// +// 4. transcript: "to be or not to be" +// is_final: true +// +// 5. transcript: " that's" +// +// 6. transcript: " that is" +// +// 7. recognition_event_type: `RECOGNITION_EVENT_END_OF_SINGLE_UTTERANCE` +// +// 8. transcript: " that is the question" +// is_final: true +// +// Only two of the responses contain final results (#4 and #8 indicated by +// `is_final: true`). Concatenating these generates the full transcript: "to be +// or not to be that is the question". +// +// In each response we populate: +// +// * for `MESSAGE_TYPE_TRANSCRIPT`: `transcript` and possibly `is_final`. +// +// * for `MESSAGE_TYPE_END_OF_SINGLE_UTTERANCE`: only `event_type`. +message StreamingRecognitionResult { + // Type of the response message. + enum MessageType { + // Message contains a (possibly partial) transcript. + MESSAGE_TYPE_TRANSCRIPT = 0; + + // Event indicates that the server has detected the end of the user's speech + // utterance and expects no additional speech. Therefore, the server will + // not process additional audio (although it may subsequently return + // additional results). The client should stop sending additional audio + // data, half-close the gRPC connection, and wait for any additional results + // until the server closes the gRPC connection. This message is only sent if + // `single_utterance` was set to `true`, and is not used otherwise. + MESSAGE_TYPE_END_OF_SINGLE_UTTERANCE = 1; + } + + // Type of the result message. + MessageType message_type = 1; + + // Transcript text representing the words that the user spoke. + // Populated if and only if `event_type` = `RECOGNITION_EVENT_TRANSCRIPT`. + string transcript = 2; + + // If `false`, the `StreamingRecognitionResult` represents an + // interim result that may change. If `true`, the recognizer will not return + // any further hypotheses about this piece of the audio. May only be populated + // for `event_type` = `RECOGNITION_EVENT_TRANSCRIPT`. + bool is_final = 3; +} + +// Instructs the speech recognizer how to process the audio content. +message InputAudioConfig { + // *Required* Audio encoding of the audio content to process. + AudioEncoding audio_encoding = 1; + + // *Required* Sample rate (in Hertz) of the audio content sent in the query. + // Refer to [Cloud Speech API documentation](/speech/docs/basics) for more + // details. + int32 sample_rate_hertz = 2; + + // *Required* The language of the supplied audio as a + // [BCP-47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt) language tag. + // For example: "en-US". The Cloud Conversation Engine does not do + // translations. See + // [Language Support](/speech/docs/languages) for a + // list of the currently supported language codes. + string language_code = 3; + + // *Optional* The collection of phrase hints which are used to boost accuracy + // of speech recognition. + // Refer to [Cloud Speech API documentation](/speech/docs/basics#phrase-hints) + // for more details. + repeated string phrase_hints = 4; +} + +// Instructs the speech recognizer how to process the streamed audio content. +message StreamingInputAudioConfig { + // *Required* Instructs the speech recognizer how to process the audio. + InputAudioConfig config = 1; + + // *Optional* If `true`, the recognizer will detect a single spoken utterance. + // When it detects that the user has paused or stopped speaking, it will cease + // recognition. + bool single_utterance = 2; +} + +// Represents the natural language text to be processed. +message TextInput { + // *Required* The natural language text to be processed. Text length must + // not exceed 256 bytes. + string text = 1; + + // *Required* The BCP-47 language of this conversational query. + string language_code = 2; +} + +// Events allow for matching intents by event name instead of the natural +// language input. For instance, input `` can trigger a personalized welcome response. +// The parameter `name` may be used by the agent in the response: +// `“Hello #welcome_event.name! What can I do for you today?”`. +message EventInput { + // *Required* The unique identifier of the event. + string name = 1; + + // *Optional* The collection of parameters associated with the event. + google.protobuf.Struct parameters = 2; +} + +// Audio encoding of the audio content sent in the conversational query request. +// Refer to the [Cloud Speech API documentation](/speech/docs/basics) for more +// details. +enum AudioEncoding { + // Not specified. + AUDIO_ENCODING_UNSPECIFIED = 0; + + // Uncompressed 16-bit signed little-endian samples (Linear PCM). + AUDIO_ENCODING_LINEAR16 = 1; + + // [`FLAC`](https://xiph.org/flac/documentation.html) (Free Lossless Audio + // Codec) is the recommended encoding because it is lossless (therefore + // recognition is not compromised) and requires only about half the + // bandwidth of `LINEAR16`. `FLAC` stream encoding supports 16-bit and + // 24-bit samples, however, not all fields in `STREAMINFO` are supported. + AUDIO_ENCODING_FLAC = 2; + + // 8-bit samples that compand 14-bit audio samples using G.711 PCMU/mu-law. + AUDIO_ENCODING_MULAW = 3; + + // Adaptive Multi-Rate Narrowband codec. `sample_rate_hertz` must be 8000. + AUDIO_ENCODING_AMR = 4; + + // Adaptive Multi-Rate Wideband codec. `sample_rate_hertz` must be 16000. + AUDIO_ENCODING_AMR_WB = 5; + + // Opus encoded audio frames in Ogg container + // ([OggOpus](https://wiki.xiph.org/OggOpus)). + // `sample_rate_hertz` must be 16000. + AUDIO_ENCODING_OGG_OPUS = 6; + + // Although the use of lossy encodings is not recommended, if a very low + // bitrate encoding is required, `OGG_OPUS` is highly preferred over + // Speex encoding. The [Speex](https://speex.org/) encoding supported by + // Cloud Conversation Engine API has a header byte in each block, as in MIME + // type `audio/x-speex-with-header-byte`. + // It is a variant of the RTP Speex encoding defined in + // [RFC 5574](https://tools.ietf.org/html/rfc5574). + // The stream is a sequence of blocks, one block per RTP packet. Each block + // starts with a byte containing the length of the block, in bytes, followed + // by one or more frames of Speex data, padded to an integral number of + // bytes (octets) as specified in RFC 5574. In other words, each RTP header + // is replaced with a single byte containing the block length. Only Speex + // wideband is supported. `sample_rate_hertz` must be 16000. + AUDIO_ENCODING_SPEEX_WITH_HEADER_BYTE = 7; +} diff --git a/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/entity_type.proto b/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/entity_type.proto new file mode 100644 index 00000000..46edf4f8 --- /dev/null +++ b/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/entity_type.proto @@ -0,0 +1,187 @@ +// Copyright 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.conversation.v1alpha; + +import "google/api/annotations.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/cloud/conversation/v1alpha;conversation"; +option java_multiple_files = true; +option java_outer_classname = "EntityProto"; +option java_package = "com.google.cloud.conversation.v1alpha"; + +// Represents an entity type. +// Entity types represent concepts and serve as a tool for extracting parameter +// values from natural language queries. +message EntityType { + // *Optional* Represents an entity. + message Entity { + // *Required* + // For `KIND_MAP` entity types: + // A canonical name to be used in place of synonyms. + // For `KIND_LIST` entity types: + // A string that can contain references to other entity types (with or + // without aliases). + string value = 1; + + // *Required* A collection of synonyms. For `KIND_LIST` entity types this + // must contain exactly one synonym equal to `value`. + repeated string synonyms = 2; + } + + // Represents kinds of entities. + enum Kind { + // Not specified. This value should be never used. + KIND_UNSPECIFIED = 0; + + // Map entity types allow mapping of a group of synonyms to a canonical + // value. + KIND_MAP = 1; + + // List entity types contain a set of entries that do not map to canonical + // values. + KIND_LIST = 2; + } + + // Represents different entity type expansion modes. Automated expansion + // allows an agent to recognize values that have not been explicitly listed in + // the entity (for example, new kinds of shopping list items). + enum AutoExpansionMode { + // Auto expansion is turned off for the entity. + AUTO_EXPANSION_MODE_UNSPECIFIED = 0; + + // Allows an agent to recognize values that have not been explicitly + // listed in the entity. + AUTO_EXPANSION_MODE_DEFAULT = 1; + } + + // *Required* The unique identifier of the entity type. + // Format: `projects//agents//entityTypes/`. + string name = 1; + + // *Required* The name of the entity. + string display_name = 2; + + // *Required* Indicates the kind of entity type. + Kind kind = 3; + + // *Optional* Indicates whether the entity type can be automatically + // expanded. + AutoExpansionMode auto_expansion_mode = 4; + + // *Optional* The collection of entities associated with the entity type. + repeated Entity entities = 6; +} + +// The request to list all entity types in the specified agent. +message ListEntityTypesRequest { + // *Required* The name of the agent to list entity types from. + // Format: `projects//agents/`. + string parent = 1; +} + +// The message returned from the ListEntityTypes method. +message ListEntityTypesResponse { + // The list of entity types in the specified agent. + // Only name and display_name will be returned. + repeated EntityType entity_types = 1; +} + +// The request to retrieve the specified entity type. +message GetEntityTypeRequest { + // *Required* The name of the entity type to retrieve. + // Format: `projects//agents//entitityTypes/`. + string name = 1; +} + +// The request to create a new entity type in the specified agent. +message CreateEntityTypeRequest { + // *Required* The name of the agent to create a new entity type in. + // Format: `projects//agents/`. + string parent = 1; + + // *Required* The entity resource to create. `EntityType.name` must be + // empty. + EntityType entity_type = 2; +} + +// The request to update the specified entity type. +message UpdateEntityTypeRequest { + // *Required* The entity type to update. + EntityType entity_type = 1; +} + +// The request to update the specified entity type. +message DeleteEntityTypeRequest { + // *Required* The name of the entity type to delete. + // Format: `projects//agents//entityTypes/`. + string name = 1; +} + +// The request to update or create multiple entity types in the specified agent. +message BatchUpdateEntityTypesRequest { + // *Required* The name of the agent to update or create entity types in. + // Format: `projects//agents/`. + string parent = 1; + + // *Required* The collection of entity types to update or create. + repeated EntityType entity_types = 2; +} + +// The message returned from the BatchUpdateEntityTypes method. +message BatchUpdateEntityTypesResponse { + // The collection of updated or created entity types. + repeated EntityType entity_types = 1; +} + +// The request to create multiple new entities for the specified entity type. +message BatchCreateEntitiesRequest { + // *Required* The name of the entity type to create entities in. + // Format: `projects//agents//entityTypes/`. + string parent = 1; + + // *Required* The collection of entities to create. + repeated EntityType.Entity entities = 2; +} + +// The request to update entities for the specified entity type. Existing +// entities are replaced. +message BatchUpdateEntitiesRequest { + // *Required* The name of the entity type to update the entities in. + // Format: `projects//agents//entityTypes/`. + string parent = 1; + + // *Required* The collection of new entities to replace the existing entities. + repeated EntityType.Entity entities = 2; +} + +// The request to delete all entities from the specified entity type. +message BatchDeleteEntitiesRequest { + // *Required* The name of the entity type to delete entries for. + // Format: `projects//agents//entityTypes/`. + string parent = 1; + + // *Required* The collection of entities to delete. Only the canonical `value` + // must be filled in. + repeated EntityType.Entity entities = 2; +} diff --git a/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/intent.proto b/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/intent.proto new file mode 100644 index 00000000..ccc6cb68 --- /dev/null +++ b/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/intent.proto @@ -0,0 +1,356 @@ +// Copyright 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.conversation.v1alpha; + +import "google/api/annotations.proto"; +import "google/cloud/conversation/v1alpha/context.proto"; +import "google/protobuf/struct.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/cloud/conversation/v1alpha;conversation"; +option java_multiple_files = true; +option java_outer_classname = "IntentProto"; +option java_package = "com.google.cloud.conversation.v1alpha"; + +// Represents user intent. +// Intents convert a number of user expressions or patterns into an action. An +// action is an extraction of a user command or sentence semantics. +message Intent { + // Represents an example or template that the agent is trained on. + message TrainingPhrase { + // Represents a part of a training phrase. + message Part { + // *Required* The text corresponding to the example or template, + // if there are no annotations. For + // annotated examples, it is the text for one of the example's parts. + string text = 1; + + // *Optional* The entity type name prefixed with `@`. This field is + // required for the annotated part of the text and applies only to + // examples. + string entity_type = 2; + + // *Optional* The parameter name for the value extracted from the + // annotated part of the example. + string alias = 3; + + // *Optional* Indicates whether the text was manually annotated by the + // developer. + bool user_defined = 4; + } + + // Represents different types of training phrases. + enum Type { + // Not specified. This value should be never used. + TYPE_UNSPECIFIED = 0; + + // Examples do not contain @-prefixed entity type names, but example parts + // can be annotated with entity types. + TYPE_EXAMPLE = 1; + + // Templates are not annotated with entity types, but they can contain + // @-prefixed entity type names as substrings. + TYPE_TEMPLATE = 2; + } + + // *Required* The unique identifier of this training phrase. + string name = 1; + + // *Required* The type of the training phrase. + Type type = 2; + + // *Required* The collection of training phrase parts (can be annotated). + // Fields: `entity_type`, `alias` and `user_defined` should be populated + // only for the annotated parts of the training phrase. + repeated Part parts = 3; + + // *Optional* Indicates how many times this example or template was added to + // the intent. Each time a developer adds an existing sample by editing an + // intent or training, this counter is increased. + int32 times_added_count = 4; + } + + // Represents the result of matching an intent. + message Result { + // Represents intent parameters. + message Parameter { + // *Required* The name of the parameter. + string display_name = 1; + + // *Optional* The definition of the parameter value. It can be: + // - a constant string, + // - a parameter value defined as `$parameter_name`, + // - an original parameter value defined as `$parameter_name.original`, + // - a parameter value from some context defined as + // `#context_name.parameter_name`. + string value = 2; + + // *Optional* The default value to use when the `value` yields an empty + // result. + // Default values can be extracted from contexts by using the following + // syntax: `#context_name.parameter_name`. + string default_value = 3; + + // *Optional* The name of the entity type, prefixed with `@`, that + // describes values of the parameter. If the parameter is + // required, this must be provided. + string entity_type_display_name = 4; + + // *Optional* Indicates whether the parameter is required. That is, + // whether the intent cannot be completed without collecting the parameter + // value. + bool mandatory = 5; + + // *Optional* The collection of prompts that the agent can present to the + // user in order to collect value for the parameter. + repeated string prompts = 6; + + // *Optional* Indicates whether the parameter represents a list of + // values. + bool is_list = 7; + } + + // Corresponds to the `Response` field in API.AI console. + message Message { + // The text response message. + message Text { + // *Optional* The collection of the agent's responses. + repeated string text = 1; + } + + // The image response message. + message Image { + // *Optional* The public URI to an image file. + string image_uri = 1; + } + + // The quick replies response message. + message QuickReplies { + // *Optional* The title of the collection of quick replies. + string title = 1; + + // *Optional* The collection of quick replies. + repeated string quick_replies = 2; + } + + // The card response message. + message Card { + // *Optional* Contains information about a button. + message Button { + // *Optional* The text to show on the button. + string text = 1; + + // *Optional* The text to send back to the Cloud Conversation Engine + // API or a URI to open. + string postback = 2; + } + + // *Optional* The title of the card. + string title = 1; + + // *Optional* The subtitle of the card. + string subtitle = 2; + + // *Optional* The collection of card buttons. + repeated Button buttons = 3; + } + + // Represents different platforms that a rich message can be intended for. + enum Platform { + // Not specified. + PLATFORM_DEFAULT = 0; + + // Facebook. + PLATFORM_FACEBOOK = 1; + + // Slack. + PLATFORM_SLACK = 2; + + // Telegram. + PLATFORM_TELEGRAM = 3; + + // Kik. + PLATFORM_KIK = 4; + + // Skype. + PLATFORM_SKYPE = 5; + } + + // *Required* The rich response message. + oneof message { + // The text response. + Text text = 1; + + // The image response. + Image image = 2; + + // The quick replies response. + QuickReplies quick_replies = 3; + + // The card response. + Card card = 4; + + // The response containing a custom payload. + google.protobuf.Struct payload = 5; + } + + // *Optional* The platform that this message is intended for. + Platform platform = 6; + } + + // *Optional* The name of the action associated with the intent. + string action = 1; + + // *Optional* The collection of contexts that are activated when the intent + // is matched. Context messages in this collection should not set the + // parameters field. + // Format: `projects//agents//sessions/*/contexts/`. + repeated Context output_contexts = 2; + + // *Optional* Indicates whether to delete all contexts in the current + // session when this intent is matched. + bool reset_contexts = 3; + + // *Optional* The collection of parameters associated with the intent. + repeated Parameter parameters = 4; + + // *Optional* The collection of rich messages corresponding to the + // `Response` field in API.AI console. + repeated Message messages = 5; + + // *Optional* The list of platforms for which the first response will be + // taken from among the messages assigned to the DEFAULT_PLATFORM. + repeated Message.Platform default_response_platforms = 6; + } + + // Represents the different states that webhooks can be in. + enum WebhookState { + // Not specified. This value should be never used. + WEBHOOK_STATE_UNSPECIFIED = 0; + + // Webhook is disabled in the agent and in the intent. + WEBHOOK_STATE_DISABLED = 1; + + // Webhook is enabled in the agent and in the intent. + WEBHOOK_STATE_ENABLED = 2; + + // Webhook is enabled in the agent and in the intent. Also, each slot + // filling prompt is forwarded to the webhook. + WEBHOOK_STATE_ENABLED_FOR_SLOT_FILLING = 3; + } + + // *Required* The unique identifier of this intent. + // Format: `projects//agents//intents/`. + string name = 1; + + // *Required* The name of this intent. + string display_name = 2; + + // *Optional* The priority of this intent. Higher numbers represent higher + // priorities. Zero or negative numbers mean that the intent is disabled. + int32 priority = 3; + + // *Optional* Indicates whether this is a fallback intent. + bool is_fallback = 4; + + // *Optional* Indicates whether Machine Learning is enabled for the intent. + bool ml_enabled = 5; + + // *Optional* Indicates whether webhooks are enabled for the intent. + WebhookState webhook_state = 6; + + // *Optional* The list of contexts required for the intent to be triggered. + repeated string input_contexts = 7; + + // *Optional* The collection of event names that trigger the intent. + // If the collection of input contexts is not empty, all of the contexts must + // be present in the active user session for an event to trigger this intent. + repeated string events = 8; + + // *Optional* The collection of examples/templates that the agent is + // trained on. + repeated TrainingPhrase training_phrases = 9; + + // *Optional* The result of matching this intent. + Result result = 10; +} + +// The request to list all intents in the specified agent. +// Field `training_phrases` is not populated in returned intents. +message ListIntentsRequest { + // *Required* The name of the agent to list intents from. + // Format: `projects//agents/`. + string parent = 1; +} + +// The message returned from the ListIntents method. +message ListIntentsResponse { + // The list of intents in the specified agent. + repeated Intent intents = 1; +} + +// The request to retrieve the specified intent. +message GetIntentRequest { + // *Required* The name of the intent to retrieve. + // Format: `projects//agents//intents/`. + string name = 1; + + // *Optional* The resource view to apply to the returned intent. + IntentView intent_view = 2; +} + +// The request to create an intent in the specified agent. +message CreateIntentRequest { + // *Required* The name of the agent to create the intent in. + // Format: `projects//agents/`. + string parent = 1; + + // *Required* The intent to create. + Intent intent = 2; + + // *Optional* The resource view to apply to the returned intent. + IntentView intent_view = 3; +} + +// The request to update the specified intent. +message UpdateIntentRequest { + // *Required* The intent to update. + Intent intent = 1; + + // *Optional* The resource view to apply to the returned intent. + IntentView intent_view = 2; +} + +// The request to delete the specified intent. +message DeleteIntentRequest { + // *Required* The name of the intent to delete. + // Format: `projects//agents//intents/`. + string name = 1; +} + +// Represents the options for views of an intent. +// An intent can be a sizable object. Therefore, we provide a resource view that +// does not return training phrases in the response by default. +enum IntentView { + // Training phrases field is not populated in the response. This is the + // default value. + INTENT_VIEW_BASIC = 0; + + // All fields are populated in the `responseText` selection. + INTENT_VIEW_FULL = 1; +} diff --git a/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/session_entity_type.proto b/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/session_entity_type.proto new file mode 100644 index 00000000..782474e0 --- /dev/null +++ b/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/session_entity_type.proto @@ -0,0 +1,92 @@ +// Copyright 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.conversation.v1alpha; + +import "google/api/annotations.proto"; +import "google/cloud/conversation/v1alpha/entity_type.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/cloud/conversation/v1alpha;conversation"; +option java_multiple_files = true; +option java_outer_classname = "UserUserEntityProto"; +option java_package = "com.google.cloud.conversation.v1alpha"; + +// Extends or replaces a developer entity type at the +// user session level. +message SessionEntityType { + // The types of modifications for a session entity type. + enum EntityOverrideMode { + // Not specified. This value should be never used. + ENTITY_OVERRIDE_MODE_UNSPECIFIED = 0; + + // The collection of session entities overrides the collection of entities + // in the corresponding developer entity type. + ENTITY_OVERRIDE_MODE_OVERRIDE = 1; + + // The collection of session entities extends the collection of entities in + // the corresponding developer entity type. Calls to + // `CreateSessionEntityType`, `UpdateSessionEntityType` and + // `GetSessionEntityType` return the full collection of entities from the + // developer entity type and the session entity type. + ENTITY_OVERRIDE_MODE_SUPPLEMENT = 2; + } + + // *Required* The unique identifier of this session entity type. + // Format: `projects//agents//sessions//entityTypes/`. + string name = 1; + + // *Required* Indicates whether the additional data should override or + // supplement the developer entity type definition. + EntityOverrideMode entity_override_mode = 2; + + // *Required* The collection of entities associated with this session entity + // type. + repeated EntityType.Entity entities = 3; +} + +// The request to retrieve the specified session entity type. +message GetSessionEntityTypeRequest { + // *Required* The name of the session entity type to retrieve. + // Format: `projects//agents//sessions//entityTypes/`. + string name = 1; +} + +// The request to create a new entity type in the specified session. +message CreateSessionEntityTypeRequest { + // *Required* The name of the session to create a new entity type in. + // Format: `projects//agents//sessions/`. + string parent = 1; + + // *Required* The session entity type to create. + SessionEntityType session_entity_type = 2; +} + +// The request to update the specified session entity type. +message UpdateSessionEntityTypeRequest { + // *Required* The session entity type to update. + SessionEntityType session_entity_type = 1; +} + +// The request to delete the specified session entity type. +message DeleteSessionEntityTypeRequest { + // *Required* The name of the session entity type to delete. + // Format: `projects//agents//sessions//entityTypes/`. + string name = 1; +} diff --git a/conversation/Conversation/app/src/main/res/drawable/bubble_incoming.xml b/conversation/Conversation/app/src/main/res/drawable/bubble_incoming.xml new file mode 100644 index 00000000..23ecf580 --- /dev/null +++ b/conversation/Conversation/app/src/main/res/drawable/bubble_incoming.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + diff --git a/conversation/Conversation/app/src/main/res/drawable/bubble_outgoing.xml b/conversation/Conversation/app/src/main/res/drawable/bubble_outgoing.xml new file mode 100644 index 00000000..ab8e09c0 --- /dev/null +++ b/conversation/Conversation/app/src/main/res/drawable/bubble_outgoing.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + diff --git a/conversation/Conversation/app/src/main/res/drawable/ic_keyboard.xml b/conversation/Conversation/app/src/main/res/drawable/ic_keyboard.xml new file mode 100644 index 00000000..28916547 --- /dev/null +++ b/conversation/Conversation/app/src/main/res/drawable/ic_keyboard.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/conversation/Conversation/app/src/main/res/drawable/ic_mic.xml b/conversation/Conversation/app/src/main/res/drawable/ic_mic.xml new file mode 100644 index 00000000..ee17a8f2 --- /dev/null +++ b/conversation/Conversation/app/src/main/res/drawable/ic_mic.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/conversation/Conversation/app/src/main/res/drawable/ic_send.xml b/conversation/Conversation/app/src/main/res/drawable/ic_send.xml new file mode 100644 index 00000000..573011d2 --- /dev/null +++ b/conversation/Conversation/app/src/main/res/drawable/ic_send.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/conversation/Conversation/app/src/main/res/layout/activity_main.xml b/conversation/Conversation/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..548bfa4c --- /dev/null +++ b/conversation/Conversation/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/conversation/Conversation/app/src/main/res/layout/item_conversation.xml b/conversation/Conversation/app/src/main/res/layout/item_conversation.xml new file mode 100644 index 00000000..67298fa1 --- /dev/null +++ b/conversation/Conversation/app/src/main/res/layout/item_conversation.xml @@ -0,0 +1,37 @@ + + + + + + + diff --git a/conversation/Conversation/app/src/main/res/mipmap-hdpi/ic_launcher.png b/conversation/Conversation/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..41251130034ad5d92e3225f43bc59a4f83646c52 GIT binary patch literal 4379 zcmV+$5#;WPP)L6`|urIKARC6%YZ?R0y0NNdT4#K9c~6;lqFC9W-U} zF9Ya$x9)PE6aQ9%f%wPR-bwHnBf;|t{%O>7}DPcQz_uryY>ElN2txqttsz8?xnbO2!21yfc$uHZB)#;H{Z z$wLQDd-bnRgP@>DuV zWc2Uv5$PkIcLpc1d-w<1hFf_qspr@iyPkWl2Y@(L4k7@?b+b3#VJ2lyfdlbrT|xhG-sZ9|+FI6=FPQKg%P1U32?Dq5d2SyZ=cl6cvfpFqoy*!< z_H{Uye*J^%$CyYSNz5PsY#&)Lh$x{A=8UgBd47&J4IQZZZ!2)%p4&kLkVNeG%<(iq zYMY4uZNk-~M$h>B=8XVC9SKOJ=jM7%&yW?wwqtA{3}OaL1u0G!^#OjAos?!-7CKT% z-FhJTs%sJ4_zpBd5K#aL78e`^2_TPoMOR?Z3BYnv)r8Z;Joc8=Go)tpl99Z_U@&0c za`Tx-5OCvA6P>00Q}0Bb5C9k&0Rjx25Dm3$hei~MWGmSFmGNV5Q|6$KaFDos?0;mL z$-SRsm2U*1kcbdHy@05T(9}gg86?pwO`@|zZa2=EaW4)nUxX|npn*XkP)q9r66njp z1=sZJ_qVr8N~!=@?RSvs*1M0KJztud8xjD^sAD7p3QR0}#u9dl8@MLVLT9VTm@6Su zGf*QLl0^iM03e9Mp|(NLK(>n@z)5ok_5GcbZeMH7VWXxk9b}|m^sKb!bwpsY@724d z&V5o$i3m4cDse%!M5l`g=>rGh=zIT!#SD)i2tp+6yhCamc7T!0xHP+0w>2M>9ytMk z+XILIBrkvDqWq5zUvxg$KB%xE03iD>VBZpGSsktPJ;l*pag4JY^RLZGq|763O9 zG(iduMFJGtwmIc`lJlM~EPV}t*`6e=Z(Mc5xbYY4zGhJJ)=IWQuq+7~4(9J7S_GKC zpRhTyKWV55jTom&%#8EzeEE7W3L(hYO z{~Yx;1*@x=%@CwCBM>A@u#_Mf%}4xQFmUlXz~CI&<`-z1CW66WB-*ni(9<)JJ>dp0 z0E-EA(t_kz$|m~{t8En*d$g5Zf#gIiM5403VbSOr9)o)VK<|t|w~6O(`ZiQw2R6+} z5EH<*YXE?u6Jzo)hEw_del9L9{(SxV^$X_CojY~Hgb7#OcH3>&EM2o~_>crd%CQR;(f!3qSxmKDuK z;h1!}<_Jz%S=o+juf6u3Fq}gGj>WmhQV{~+2T%*(=(Ep0yCpwA|K31YPRf4n>_;IZ z93r<7@_uQ7kyzbW-aY4%yngXaM0~eCUbt^jaSfoS$%>M=`!I=*n>!p#K*1uNk|o__t|9Wpcui2z6m zi-Cqn1x_?3uy9k)jOFp&zGza`U%cU}G>_=vD!&hJm;=iFu}@6F&>RF@j<<&HTsot zBmNbD9uG+L@1Nlrp66el_|1&P!{go*X!J3&#E1)Y4D3yCqODuE{#C&-#woB&30BY;6xsGmfD;U#E^+BEWk$kc--709!PWQ zk#2Rp?q4=m=#aR1bPAj(6bd!Yn>X(>m2V{4Ds{S*mzRG5hswVGX0SUcI@e%Q92gRe z05?A{HrVX_+?6B7+FgcFr1YE&^U5|%moe?~FES>LN;0$4h0`_9F@IP9EVN&t#%>Y(ZZ;sMoYAu7p?AOjp%`UAlb#T*os+_|rQE83_&wA6d#=t&6&G7KZL z1Fj~aeqq6({B<>ae9_Zg7~Jf_k5p$Lic2P6)z*Dvrvag!Gw*zA`}=i1^C;RNuOQ(% z@s7_SAn&?$>n{6&A9ri1si_ykp~}{+@sVzb=v*iqP2i|O9|$ZZER~aL{bu>^cNBhX zyWMt>SySV$UAeC60jQaCt5WoHT^mwM4wZ-CK!XPlzU2p8k|Of*^1R{I^e8w`T~)bN zyW!0)f(RNAomq1;a9DzrT_gi9AE}((7^}24R4{;s-#oqj!^1}dU)l-F2`nN)x4M@D zrX|su!Y}S;;oj>1PH8(NW+5`{~a_%#9yC_t)772k&g2E`75L-max zFD@+k1VAGIjt3M1aPpC5B@ZH=33Kznh$4u4ChbZoFa4&`K_2QddGh4vV2>A^!_n8S zT{|>8JNqsti^Su5zY5Uaue~}6=m3OrkRUXL!)(I=OU8ntvN`q&KOPX2%Nka_RlN7B zZ+x$|SZow1Ld>@P>XwP>ntDbn@_0NW!~NLmtgI}rauT&NGdEs)?X?T9xZ;XebzM(s z4URNJC|awQJY=Z@lrw5Y_wGZ#1b8Dpw`#*sWFY8vu@|$pNq(u zZtsz@Jq?`zCKBLNjmG>^|^R(;~@EHJhz{_h|4DGipP+iL|7n7Ktb4FP|{%RB`F57d)DMsf$|g2qcCBW^Amf zpL-%0tc+Ey#1alCdA_L+fAQB^U+6HvXf-H$@AWY0m4B;eW=Bv|IEGmE-z$8n(jtQZ zgM_`Z2ekmFCKxQ9yQA<80FJ)sLT$L7I(6Q!2mN&JFLGXm6R~oFWE@DIUz?fTFSR{R zN~Az#k$K?W-{=N0l1PIF30*bMLcsti21kt*7nfGt_}u=|_wBOYQGiHYLfgG`!{}H0 z_HmDN3`sJqfJD{tAcBnyDLH0pP*WI^)jiZU?ERR#nBn(XR^{8GF-13043&Fy0z@lt z${K?o{N$rArva$6D^~|yqZsSUjlHT}=yC&oSfR7CMq?XuUO#<{VuRh|wY($&5$#7OJxD{8JS|+hhR9`h3;! zCoauPxf2e>QndpXqqf$BW!l+FxkZ6sEB2V=$c^p7C@Trn9JA^hHdwACavK1kfdRW2 z1)7Be0AJM9=1<+W`w0NYm3z<`fCzx6e;;q|&i}dmbFV?ki5YVAAB^G~}jKzqc1zd^z@qb{)_#qtdRJLg!eKmkX-u`0DqdUsY(m|KTK#8`K*p=*szOlAO&~^~frG}^GNTC`ZW#^%C~0i?#|sC`3gY0L5>*SbRUGGx6KNdh|Z&lqXERjK@&I6?T??eeNk1Vf2RZADKIh;wxbv_vMDsONB1BI5E07Oi)uq7oe&R)6&z{` zhZ$vlf5FVJzsa|+Lpt5Rc|ilv#pOcJzdSMImT^No)6&z7a}qYB_WDQ0+{AXNmug>? z{L+Hp;rb8toThNt2GJA_Gin2&gP+%)Ts3D`@$13pjM;bk0mWJt4}dfPsQ}y^;7(S> zfb4KkI0C8(J*g72c_#h#7XiRfp>+U!Zllv#xh14Ro{7Kfhj3H2@C8K*yZ6P)JRCjXdM%$Gi_C>F}|aMXpGT@ zp{AW!pQ#lQB_g7LyablzA+S6Ik>>)-?y?{v?&eRQ%1!YS6AF!AAYtp3Kt?*=*E z17Q>&d_A01$9f?2aQpqRJqjK<$1A(?5xX;%t~}NagRJ<<#Bz#I!ch|;6v0PK1QB9P z$SGupO#~96O^^K}Qx?CHO1QeZI<7j>UO^WibQ}T@f)1*S@pNyDd+UVlE(|BK<}9}% zK+p&Q0?h&hAi@wH?oGKjy11`{cN5NQ@SRx@g30mA4)s;JEX#X-c8ris&cYoJdi-}jf`C030;?cD(2kWLt)#^eUARp!KwXoL zNQ{k+4oC?VL%xUu_c2Jh1i=7=G7LbtFYwgK=g)USCYbZ$FK}$8UD|NSXG`%j$&OkG z*x7)KsTo7Gfefo4kJ4f#bZ|CP4F?K(f6Sq`Moim0BSXt02cJtdG?~dxv&JsSdodZEbDP-rf!x zjb_BxYPDL({&X#*i^v+_fl5k+AV@eAT=Bw7!2T0rB=a2;AKS%4*B)(Ae0hU$bV796#!9 z#DSU`b`0_jq3xJO1SUiC7h8mmxEeiAT5shC@CX{_3pT#~H^ zWdKkNg(y$@pxO7c0uXF2hohHTX|H}lLP9JhZ%?V!$C-N__WB;)h+4gcAo0du+2hNwU&d4!BtmR>Y#)-=S_``Kqifkb`~^B|oW^3Kyq z)wBx`+eb%TeLGwBSJBbY0V7SNn)KVfxwsArF$4)vjE_}fDgZ&uh9A5=_=D%OPx>1_ zh)B~?=nqATjBdIUHZ?bEsRe7xVSn-BMaf73RCliHvW09YK=C4^1Sv&n6(vuI^;jqv zY@>-we&y`aog?WPDS#$><&&D)1|wYy#l^)fw6Ow{3JVLj(#dzUwZc_7w~qu+2=VGu z^7oN4tVpGZ1!cY-de>>w-W?2Jhy3LGgN!XuD5b4}Q6Yu$+zUFo6IWDJ#1J_f0yx&! z*WaZbrRU=eX#(7p3b1{IY?SAi6k zE-_YAJ?yu1VzpY`86O|N$jYY6Ua3^BB^9&SinW!-L=o4Rh4KoNFR>t>=H*dM#xZez zVLEJE_v=--uO;FZF)AcbmwAA;3N0-yRmsW8i>Xm+XZHD%Q9cY`qqp>4^0y&t*Osmf zl+Q!?ViDU+9uYDjV(#o<{H&=KuMRS+BHlNRgcuGLLV+Rj#ZXjK)H}jfLf_EP@GZ`I z&b)c^9QN(o=Tux=98zE3c(tuf{fth&zBIo-)sGE1*gi_q_h#}HNFvuQ869ViRAomI zYcAWU5EF6B6o{cCbG@Ne-C=cuh8wzmgm(N=^5k`J^W&90cLhwG{n+;?Yc8+kvy2|j z&J1feHze6lGrr@*LZKKef#FP5$h~72%N82Nhft+m?M*}ZdqNfzlZj&{Uyi>&#aMC- z-DcP_>E4(-+i5{}u5UMO2&{rONB*R;`jK%g;)Hf}3#1CTeOUvo!@{foqI?nF(7fZ| z@Jm~7@JYfqPwiRyXCg}Ilauy z{Uqc)O{&iW+7Q@EhBO?js=B9Eq-klWE;put~~IhrrIrf!XxzZ^Ft zKm%4m2oKYD>Bu6q2Hu&yK<2i;bY)`i7vgv|4ahZP; z6hg}(6c{2;0NNC9c=&HF)LuZ&bD$2XJ;#9u|6;>~(=4d@ClhMEV?y;QPpFDp29@6~ zgXHz@P1z`0fLFgn#Dp>n@`>Cf*T{|0Sib0W6nMJeS%fG{13pJWbf^1(`kEK06HT-x z@Syby7n(0~py?7D8ZR=T;k+k2I?KQSOZzf>ml>2oBG0lYPsZlr=~LaUj7^&s^o-lF z%x^#;06L5XNWK7iGkrmSix0XKK4_D@q4T;IXcBp#N#H`q6%MptW`p_%CZw%q)RvNc z4hr8#@gaExQxRKgGXGWpPx z>J8mDd0_aF3;OG9&|PJLE}jLM{>$oz$qSy7N2>65Uo3E4M|Q~L`L8B>#HB68yOLhs z59NK~S*{QCXZygDbZ_WM;X!vY7Ys>k=t^XRYM*D*ki5XMd?wGm6wft>I_J0^Q#Xdb z9{>e@C~FyGo+-f7EI#zz^@85pTm=&t{Wk~$VcG}-`7N>x@uYcv w5#mhKI^$jm2V}}i;yCj2)R$qekN>mdzmO(cOxRgD%K!iX07*qoM6N<$g7SX{MF0Q* literal 0 HcmV?d00001 diff --git a/conversation/Conversation/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/conversation/Conversation/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..40897d71ae8a4047d78ccd7400b4de9f066dbfe1 GIT binary patch literal 6334 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 A3;+NC literal 0 HcmV?d00001 diff --git a/conversation/Conversation/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/conversation/Conversation/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..584eae61b1f1ea3ad76e0a0e5d758af9909afbb4 GIT binary patch literal 10587 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 literal 0 HcmV?d00001 diff --git a/conversation/Conversation/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/conversation/Conversation/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..a171d040a5e85df3fd387d3f6e9b52c51e4df760 GIT binary patch literal 15157 zcmV-5JIcg~P)gQMtz@St$nYKrbs6}xA^-pY00000008B#8|W402#vlzAA<1P0R9zz z&G&0V5Ppv@W(XA$|Gosj$A3M(lNtWm-gkh>Rh93*-?>w^W>X=RBs5Wk&`TgR3m}RV z!Jt$X6$~mO2pE2Jkw^=IbP$4ofKoyUMN|k)AS9$0LV7m4**?3|?mgds!rt%Mv-f{D zvpbo+J0bJ(oM-mV+|3R%@7Lb*opZE1SRVV%2#OJad`NE{dddam;|{;zh;YTg?-GR% zQ4wo|1uF~#%*Y8j|6aiVCb;J>F*CeiZKoK#Iu%dPOtjTJx8S84-u&QS)8YUe0Mutj zP>cXR#Q)weDh@dA%HOBMr57Xe6Hge^j81gHqx zj+1V?Y1UJ}{nH0;JlY1p@k9kK8bGk*w*Z9BoASN7f zRF!i>eC-FvJaWYm^IRivwpJrhTRZuGb=TtKX~FgDe-XWiSc0*x9s@$#9q|6? zH_rVgfRMN5DjGoVLJS~u?jL6!5D$-fthSlKj3NR8Gf>mau&Q=?=Cmtb{ilWTras~QPx#X{O>wG3 zPf+ySc}GJl4cKSm9hU(J_p%WbBY+sd#Eb476LTug1s4JdibmCj{=Yu_tSQ6A0E!Wy zm-K$|{{412`R;g%15F>ekUTP@pwOBGM(_WlJ0%MALP1_spBu_;x)Z=dMqfwd%T{Ycp= zyvv?Pmdu%P>%XNvq8I_TaK`tf3-2A!Y`b;=eNgc(D~myyPb4bJjsE7xBLaN4h~SMYD)pz?RWMaTZ1gB3f=pkb^V)%H>XPv%r}QPc%E)k zXmv^sKjq?A4k&b4R28!KAG-6-C2Gi?e@WQ^5;Fkr&%BMuqB|ULU!lvQs-R`hNz>1X z#lyq$;kDTv!Flgu-qweTmFHAU)@s zfZRtU=B>+c91Yl|qo(Y6;K}_8wNFSDYQN9!$6j?^JYj{h6TIuu_ueb})0qGch&7z28+jW@f3nYUaHsncYS7QVcGlF< zg(!=vLXgACJ<^8P#Rv}dVRwOgj!ld%cfXz1bKiSK1+@ZNtb z&;L!NY`_|}?>&Vki>g9OeU3QqZ$E8Lh6XcSxWs32^GfExh@U(8i|S8TXo`nM9)IEM zrx%hestTp>J?!`k1|}jyuhdKcI+LBaBO)+!PBJLxjd;bi2-yo@f7lvuN`~L~)x%D! zDim4NDgf^v0ucGyPoJLJVwdjG{wm2$aIf1310+B9&9@wU>cat~Kf!j3T4TRDq4JX% zx4vEo-J+@xy#L7;J@%!9Re35i3>bMoyNXZ{vMA?|{DQ4cHtpq)H%O zVcQx2I+2}OZs*>M0_+Y1Pkt`ci2)9(768J-_qn%n*swm8MMHg=`b_xw#*x;2+Zd5 z*)2QU>kQE^800=d?mw=SHTDCSAu`C|G(j$e>@vXc{9f)DJ^;Ap1Px*vUu0NH;p0nx4O+ISJ?|iwiUbe?iWy9l zR|85w59_N{MY{frkd00c`BpczE$Iw6K-MuM#U7+wz#xdxlR z%O0=mMjt29ih~CYd;q}VF8MjAv&SgNPEP>QiC4aRd@?e2TACrATnO$KMrO)GT}Od@ z%@7mt$CDXDD$>}NKz17c{o#oq))+v9aApuQlpBeaS7cz$QTAeZA!bExj~4+@;V@Q| zR!uzq=@(vhQz09G-r5boT;&zX$e!f~{oH__-h2a#Z=V8-fGQ}26+wZaq%dX{mTsObS@M5BNLpn+Qq_RE6K2l* z^0Mk!s}#h&T?_7ObL!Ws5HlHZK zTVD-e7{FoQzGS;3^}ASW*Q1kHaAWrcGg&0CF(05^BawSe@4-f00Ft{lkJAnWN47fDB1JOo0t8quBG%_q{x`uBKaV?i~<5r-Yi2u1FOOu*+7Y1pN` z0-N(Q*$p5^;~;q~EyaIR)9zU2EsLt&Zh6=wdzKC#|3Eyg4I6g%IM?jW3uQtc zV7X-T4v>4jC4E8%?gZ?*op}Bt!c|q+;iOX`Ux`D6M$NF9T$x#kC~Y)OY|-%j5AM)y zaU4=rdLMvrZ*Q}%dOHFf`|If^Hn)X$aqZV7J^(G8*I?Hk7@f%OA+yIAxQlyf^9KWj z2sm|Lh8EEuMQE)t=KE)So(?H@f{HbxnHib^#X=SavAeksGZc{1plovr07A`8d%Se= zaYy&|ngOd9WzX<#`y5(1Y~0x|v?UP*F<9CcB(st9hYYW7hFKqZlia~%%A#g1;QxDq z6ZZkejq<8KMUYY`A2SBE@4RJppCC<)5D`>n5Xt8ZaW?(~oO5tsxR2Vwpwg1B&8S;{ zSA8sH_f`#H_2x3iqksAGWzDV8BiJMK$Y;+e#VQCkKbZI5s(BUOzlR%V90kwdG!7O!+TvGp+j_e*#jS}t7!wk z`HlqCTe9afk59ep?oW=J2vQ(skmbHi9iE!pYf?&{bhAC>FN^xML!f)I=#GPdD~@*n zE*e0#$+7u2T!p5O-iKm>B0xdvCqk7Xgdq;6haIEfoK_O0w;THGp40w$PH)Jfsy7PX zr*EIQ$8lep`Ten>k87Z9Fc{itFSdBokVWpSWk#kW{;?Fa#*#?)Ie|z0>DKbeGY;Wy zkiQ5=YwSAdHvkhf8J{r$4j4_$Z~!nd7``4n?;{#(KH;u!ANJ+mD2T0m7rq(UGkVXJ zdp;Yrnum3m~)j_0AA?t|h$P zqNWjqLxh{YPpB$Mqx&dAG2^{6P6kq`_8DSB!nE8>wm&IRhh%;x@97UDu!KTbSz7tS zZ$ACP3;FC8RryqYChWUg`Bz4#rUEtulMZ>a+)CCt$+j4n+tVMG^yl-3_G<^)&16Xa zzr@bH4OGUZ=*A2-%3l*F?+5fRwb3&a!AfDb-(C*w&d+E#(9j^#VAjxJG;0Trq@Q3Q z*TFjLJ#42jD#UjlICgAlJ}1Yle9E3PAN%$tgG!o@GT%}(%q3B@to`5WovWAuWvOvp3c3(yQ!ZepVuOz2Q`*bs}A|0*$& zOHUuK5K=aBM{$ODw@a_W$CJ(lNP}H1Tahvn2u2JV{n(P_l{w`8)a`rwCF7U>XpdbV zdT{QNymX7IJRNBi`puccZ-Eo@-FK%Xg7BqZn_kZ;4eg}hBodJR2S$51g=VxrI3QVL z>P>$DvrowV9q#CSUnHTu~pHqoe_3J%%zs1~8 zOGcnIfJy#m@0_@A|I*kon+szH?pQlww?PXVSvzo?G@L{mSZg;5xWr`# zH8UfoH8c!l<2Z9pc2U;a{N4MGJ#zPa$fBVWA-#mibEVAc1x#J2eO`4x_?S2#LD_<$Z@bCy~VXK~?ty7{8^9=xIx3&)LtP zK5^XW*gf5yWV&(2LDesPdiYQk4g;jH=`j>7YcGJUc4e8A$_z_nQ7BeY{QArgqtmQ# zBtp35hhg-K+JSrgO&+_xzJA^N@4x@_Z+`Qemoi>F<&s{=@0C0J9HlZ^N>q7ydH9xF zZaMm(gAV%sph1I1l$Mr4DTSb65#hr#Pliq;Tv?QjH%lO5w;~{fKq$8Ws~2|;Cd5(hydolc{srl$3cH{Q5szy0<*`zJs7$v-pXZv_ws&}NKQ zcf@=~t1;yMcx!8G+s}UXv*-8Sci*#LfBp44Gk+(xsZIzQ8o;iXUJYg#Qnm4Qk;elMJaF)Bx83%;9d_7Z|FW{Oz?V$0R z>>o}$CsmgkQMQTrM5>+8pQl5m?Ib614Zs3}L5ezWhe933E(Y-1Z$~k1loOajl2LIu zQ@Bn$<&;zY>XJU?c}xrWb1(o)i0_Vw`S|3MPyTTK{r5kos;Wv65rTrfu@I~x`O zA=e3sA;|JcFhkyxB0{;>2$=JBE7ef zh!wi*i02x$qd~3&N&sNna8d~{G{D`EMU5SYcOR*+_h$(j$i__xm?n? zW}r8J6@30ZLk^J$*t1-1vhM4DatC2xLfSKlRj8*IjhcMgMjQ zoHQO&?y%jV{XKuyG*d>%9T9g#0hG9p(TtRO=xL{&c1=c&3n3s-I)zzhd=Cn`J@o+9Y#ULcg_!vU`=Rg1Xi}UBt|7SX#4ovdh{u{@_DlZ4SZ6}h3ai?=m*t=-~5?aIQ z1z9xI(zNR?)B(T^SoLSSBhxkVv4Rkxww#wy_iOpZ6O*x~wO%sOrIyMxpz!KJ@Yk zwUqjQXSt!OT$N6m@bU(2r@&yamOKa`W@v3gr)@yD8-WzW(7@3Cz~Ns}VCEoFAN#)h z?z^$EvC-7`<3fHRzQ2dh3G5l#8x73&5sSsz?!5EPDQ#_Sc2Fu<#csQ!WT#!-SWFVd zp&0;mL;+||bO6vMMv%{w#G9Kc?>cy|-}EFEQr-3bH(vVn9fl2PJI{PK`7(r~_gpz_ zM8&!mZ98BEFr%IDtU(UInqj9>?s@{*T8MGZkx?i(fqdDE7cYMC_S-Xh}_(zznczzh)-sQj~{Ih--$<=c#_I zF({q9_@V>GZQtEgNOe>844;3}u-iSdXWk4Us@S^hYtO~BlLj$^*a5-)FiC@@2u|D3 zuAsK}7z=yupb!KyUNw2mHP<`CQFwZ}OntqWhMY-`@`i zXR8HNw%q^q2T%NA2{n)Mq&K}{4BPkD1JC^OzpBNvlXkyjM~nJ{@~FZI2b3VFJh!f{ zZpl6O+_Ojs=0pCzgMT`deCVNvKFz%E1wlrEL5F=Ep?>`V0yH|-4#*?Ohyq0a|IRg1 z6XfY6@kp#;r{_-j&WYJ-0kye?w-b-qqul86SM`!Mj4L01MvS3S+|vGVl9+W)61wR|@+*7F zvQ#ic78SNpr&@r!@a754(BPcCzl0av(p-M?_`QF%xhSBz%KbBwUX=qY+a{TREGR=b zYLCx`kE~eLtQ{K~vVA(^++(Uh&_S;5O5S?wtslF@^62~p1Ro=qGG)q~WHK2KdQs4h z7hVR(og5=`R@iNt({4fBC7Io|21yNQ<}KS`iy4E{@r%B?-FBtkD3C1$vabC39*2ij z+*e$ zP*qjf{=270EdVX2!L!RvS;xU$Zzxm5^T^9WjDbD%I3iyULCE^(Af;05r)gvHLo))B?Wb_o_o;1i2!j zoe3_lyEFju7Qi94cYqfG8^iWh=zrcpZ`FMFkgC4g*fy4BL3<&=+d!u+%W6R%p=_T6 zv>G_b(ac~?B87sX;Zp_pk{_lKfT43-OHh*1V?72Sqw5-g9S-pO;x7Mxj@j*_!LgQR z?xWMx(>~-cWUw)G1~&Q#iDZHkEop~5-rSF?#93BoxzQqx+#*3r*_&I4dWz5PD=5Y(~dC&r{7tf;ANa{Ay9>5BXlKR6?ZL?V^w z1GF{Pa?SmJEtB(3e7ix(H|_BOgUlDbM!<}0*r3zzd&o~&o8jJ_)A|Mq1Z8E4A{(D+ zbDUspprWE;djN#36roUPXkYA%xBAgXVqlwq%}brc+ynBPsT7M5WQAtNM-44+cBdJR zjYq!v{zV@q(kA2jHX4i$zgD?s?V4o3HNZ#+wjx6iZ&y-MG9qyAU%kX``|lf-Ug~?v z56-%AqA&Sj6wGO6Xx_Xe8%yi-txHzT_NaLB2-Vf3%CePBiP`g7?q}}17~bg|JVhAQ za<3gvq!1LM(P-secir_BPySfQAQ?B_c;il)6lfp>0d>wUyZMfkbbod_1D-mQADoT+ zyjk+g?twOSZ3GP9*l=L*pOZeLBNK62S8ACU3(ydxe_T$?AotT8k$kM8x~90G5xA>}&)L%+UjylY z!x~9uJiKPZ|E0ZK)nqXMZyJ=A6-GBaym0QywXN^M58Qj{kr_gzl2QQ5Djlla__x4h z(M&oq@qhykDD%uF77WbPC$npGbo=eM|3RR#=fe-RDy?C9f3<1IL5Iv{&TpLU+(Ys+ zZ`r=q(=Cl=S7YctR0%S{p1lk?ug6rf@uKV-`?Pv&sGzJC$HqV_R47c8{T>aVd zWI%fQkVhYVbW#o*um&KlP)TK)$if403sRd*L#zwyDk_3zDTe%R!80LCC`kQoKqA40&$mV0z-JROL_ zk@2cOojiH+P@kO!s89;uU;gr!gGY=QF^QQ2`Ai(g#**7^PrIs*OKA7VRAcvU#V+|J zAhOXuZf%R)jA>N-D3LPns z#eg#6Br$_LsSv<9=lmx5H=)6?V;6t4=wSd!-%ZPWpt5bbVQ#)uy)oAE)T{M>fM#qF zn7yN$#V`OeB7y~sO8+@hUE9n-eCg%o<;N^sxbVmD-)~{xmvM7`nd$8~H-HT95A!-Njb3iO9Z)?CSsMi3A~kVzDFujMsTz>c9ODp#$kvjZyp$}@`4 zFDC*NyB`3%Mj)gT_1e_^ zWhOY!d40+GpMLu3FQ0J22`^ffHH4W1A-~odtM0!qF0v<) zk{|$aR0Q$YkS9v7=CM;}i`}b3;)-Un#{!TV>svhk*V_4XauKn~6+SYkk`_X%WMpUzLmXZ^3dv+UXaXu*BXkUb$2V0kKdy;qGX&DYHT>_z~Io_K$O%5@LQH)pS@ zee2_<2f;mF?wD6uR6Yo@fHBE?Io6s&01zdl|L(i*{*TNtsgq43@HK?&20^~V=hVo| z3G|olwbx$HF!MlW?p3A7gp*ET>CLxlDRc><_-K&fn#{si0bnByqHN^n?1mt>Xa;7* zWB1N)Zm!ENd-@{;KV+9h696KoTr%h3<vdNw~M1btF zXl-L6_V}xf*Z15Dv3KMW1v-aPciHpsw!2E#Z)nm%_rZrkp}jNY{C7=F%|nkr{`fA& zxxOJ85*g%ChUaPjK|XVN)9#js zhWA&>kUfckJd@vH;Faoz>soBPHH-Jp+cBwRoLpYwst%Q#9_wB|@`-_iqGQl&AYXBa z+#lGK=p4qU>62RqfBAcr5(R=jO&vfwnM}S_TU+}`W<9~P4Gj%``6&A-Jnm4L{v+$H zx8C|rW(NAKjJ|&uGe@KYHc0Zz^UI$4YpnUnrz?oi?(NAuA{7W3xxXSZj!KoNkRS33 zRwU!gj(v0X=>S^X(F!0T`G0;5z~`)B0A*qX7yMw?J*NI<>?33m0tFkyH+Erl9AIwr z=JbwZM}`qR#1JG(HKo((Ik8x5W+qMg=Ww!xcTOr2WHZzz5Dm?zi(-2 z>4cJ!l5qg4wAK*f2TguKbM+d%WYSNScz=r+)o^E1BInF*x6exG2K;^=x54&Nvoyv|vfbd2EF$i`T)v5hjB456! zG90qdmmukmR0?vfwN7TP6B)xuW%Bt=p1mY<9TgI0uBE={Q{v0y--2s@-N3difOq8KKkYBFYypMp7TiRYf?cF(-3;8|XU&x<#12Bz1qClxcfrED+GW@NF z_I}w4%Pql20g(on0c|rrv96Af-DS5@B0`~oh_aKgkIB!>*tl#~YSm@`7XdLsLUCKp*|X;!v#6=H+Ax5)M%-yv-$T|8%6ODx@zP4USmh>U;L(?MzRCcXS+&-@!e_9)QMl4yPG zg}UpRdshbag2*F_id86JgucCeeRZQo5j~1FZO!$({xA2GDgu-%cM^;0<3<4m5R39- z%AWZrzr(hA<}dT;-J}R?^!pGHdckhyg?19>LD&5S3j>#m90hyzIEQuqd7 z0NG{H3vQY7c1?9+ai12%B8z55d5NY_?K5p!>mumk<54I{eM1U~e?f2*QpUq{C zAc*Vk`?b}U*;H;0pt(`WK6zy&DFAeGWdd6l_T zwK}z}6JHRoS?;vF@t+ED7)tVdx9=w;`937~XP@(ZkvH-uwZ_zy>qYkTdViKtNCx1$ zyR<}s`=4FDY{~N08So(RMf6G*(G!No9!$p*>1~(z=Ajl-rzSfS9GSt8IRGJwq&~#* z*~(l%{>abFSk&D1!pm!`S9V$W8Yr|Bh`ZWV?zG9vZb`J+1tE(v5e8ce%4=55Yh&hZ zkobn|Iq%)1vut@8x!q=pv~+wLvZs04i+s)bCqJ0c>Li@s&RKGsU+N3l|*5#A4 zm)6w(=fkEuzg4%RtDO2`zQy_LXx)mjXwXsndK-pJvb({QPU}M^@`~BR-|L9`U ziy5>s)vQe|^uIm( z$bHQKfBq=49w~f2%F1nuY8 zCg}^MzGz*sz~1n{10_niOB+dcbr9K8xrMJQahkn9G7G$=i0P+5Xn*!y~E&A~E6`w6!+WJCY%A!hj>{qEQ!ieU(Y>`ES_w>QRw$oU0`_weK zB!CbWyV|#!|?uDc4_W0`860b8yap|n@BYK z9BAhRp^$_E9}SHJlFDrNV-g5u(5X%u;gW3I|$+Jh&s|9 zBEW)zV(k*$?DmvpjxzE`{!Q4RleNG5Wbw1&{evoeO;8`4Wfe7dm78+@9*11~y=u$jVmQy=lt)S1LOOX}l3GCJ;shx}%^Xiac>!()`LD5WxJ zb{pD+_!1>NGM z*kFw^H>xE-(lEJl>i~2jKTEe~cgfFwsV8;1zs^pjPkeX&H>(qgI`7_|LjEtZ0l+5; zlt~nLZpvP#e&?vcmyja#l@SnJr_7xV!FGUHiw$-I&q*ry1U;Vpc{`+Ep&Tnm}m zV=2bHK6OFIW77t1NxVp97QvE+How)P3Jm!j)r;iU0G_L^yWw|pm;A%<{%t<9zW72L zfIBQtLENvhvb1W|KM#DS|Df;?1Po@cyw^cic`co0w6@yP6q!A(BI9QhT@}7kY%^ym zd_9C?g4a-3GnP$r)(vpN!^QJ6T$sxkAt3Xdue_6g6V}+t>Tmvg)=4(dY@|MMQ~E4y z0|-DCz3#lP9(ehWhu$SoAb2C-&QYP0@C|4Oh{YVw>52a2_s{*ksgDqvyUP9jBvAjt z7Yyk3``0dXHk7;{+P;I@9d!-)P{1&c?vtM($V&b+Gj3nD`lt7-So@*r{ex_KC@2GP zhw?jeY0dNF?;bI-Z14QMbu8peCeMmOz1OYnc#99ARc$Xx|`nLP;Law{<*(Ns?B1CuTU69}3`@^_q z2uy-YO97jn-(xuQpXOCV8q^LxC{$>TnlbM(f9T_2$A8Gr!< zAd5bA?H(teeC+m@kwQU3JB&oD=%fD1kC4_&{_MGbmKk5e0P2>f8xzyHpB0TNgZx6) zo|7Mf+{mv1JY8LP?eFI3;!uZe~Ia-YFFGRO~O^HajQ*Hv35@-qiWem2P8jF9=tXwxa?ULUe-lCU_ z2G9ZaTR3y|y^Ci5__#qIj2czCKME9*=PM$Fql9#vH@oavOo0U6BbUMeA}9TR;k}EV+V{lr@^BOd z2b25^A!#^5uv4C|ThbRHvs`C$OHA&A?liN!{6(%A6sFNVB0tMj{>U$ANZE;Bf4u1a zu8%Yl6b&F-L2R2wu%>=oa}kfLH4pZ)mjbShgdLiZwuXL(raDAaT|o=Dyrm zpI+bRWr_@DpJq4jewc(%NM-N$@d2~T875hk)`>>7FY8n&1j;S#8CzEl? z3Yj#M?5aauTKR5zO+Q#8yClCiXyinG>NfdPVBEW6{p552`}^b*777#8MdzqBb54EZ z+20zvXaB13$O0>T4fxFXk}*BXWEcsFLS$T%;jpQF7`G=56s{JcGKCBMnh z?BqAGxHa+iEx$==eL!UdVWV3XOrl{Pmp4uOa9W`I*tijB6Jzy&ga^Ki-D~wRUUyY7Cca@RpLFb0WXA zS!iZ_)Y$UShYfAD`900CXaEA=BbQG2-Q4?Hno=!&sRDKF{n;G=) z$wq~QPWLT;mNr@lMa)zu7xEicoD=y48&Xc=ujVX$P@GdX*|S&!MZc-%_m525V3bJdD*?ey>4ITSQA>PhAV(_CqH+s^Y>61ZE!UH zuyn=Q^O|DS#%qpfkz%5~?zux{h>AWm~EMgV_RNWJ#K z#@aO-({CNId&SoxQ57Xc`ISQ1S5w^VLYL0Svc?x;1k}Mm_M|=_eD>ees?zn=$exp* z<&~b3-@B#(K%3TBo7PP?F8%DsPp;mu06@I=549*70C}&?F?(@K^IgxZe`fzLRi(EZ zK;tZsBCv2Lc-hnI{k`yC66vNoKHRk`uP7O5$ z42$elTB?VYM8c6RHUMpsKa2NgztqR;{e9FV&w63G|J^53pP|MYn~nU;3@@3{Q}P@4 zq*6MSQhI%D+@Ae$Q`1`yudJTaYTJ_P2>Aoa{fh?Rvy+5uvg?i#_d4v(Zzvf-|Gx$_ zMNI+D$ZNk-KV9O-{TI7cS8d4WQS( zl7KyP1rY!tcUS<_R%`&AJ$eUs#+y4*o?!>S(4`mw;87a9%PB)!e;SmPNECGXBSU6- zgC>8m2JnCH${3bsC?f*k3laq=;9e~44#zXJjO+$mG=RVk;&umRA%79M8G-j_^Bxop zpl}SqdtF5C@Djbyh+i~-EGq$v$bTxv_=3Q~7Z;3e8^zDEh$4z8qKG1jD58iWiYTIp jB8n)Yh$4z8qKN+ogDrxQ*CP6|00000NkvXXu0mjffw}ZB literal 0 HcmV?d00001 diff --git a/conversation/Conversation/app/src/main/res/values/attrs.xml b/conversation/Conversation/app/src/main/res/values/attrs.xml new file mode 100644 index 00000000..be2ccf3b --- /dev/null +++ b/conversation/Conversation/app/src/main/res/values/attrs.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + diff --git a/conversation/Conversation/app/src/main/res/values/colors.xml b/conversation/Conversation/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..d1685196 --- /dev/null +++ b/conversation/Conversation/app/src/main/res/values/colors.xml @@ -0,0 +1,26 @@ + + + + #FF5722 + #E64A19 + #536DFE + + #FBE9E7 + #EEEEEE + #E0E0E0 + #616161 + diff --git a/conversation/Conversation/app/src/main/res/values/dimens.xml b/conversation/Conversation/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..d0849078 --- /dev/null +++ b/conversation/Conversation/app/src/main/res/values/dimens.xml @@ -0,0 +1,25 @@ + + + + 4dp + 8dp + 16dp + + 4dp + 48dp + 64dp + diff --git a/conversation/Conversation/app/src/main/res/values/strings.xml b/conversation/Conversation/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..533453db --- /dev/null +++ b/conversation/Conversation/app/src/main/res/values/strings.xml @@ -0,0 +1,25 @@ + + + + Conversation + Write a message + Speak + This app needs to record audio and recognize your speech. + Voice + Send + Keyboard + diff --git a/conversation/Conversation/app/src/main/res/values/styles.xml b/conversation/Conversation/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..a7df0582 --- /dev/null +++ b/conversation/Conversation/app/src/main/res/values/styles.xml @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/conversation/Conversation/app/src/main/res/xml/backup_scheme.xml b/conversation/Conversation/app/src/main/res/xml/backup_scheme.xml new file mode 100644 index 00000000..95e99ca6 --- /dev/null +++ b/conversation/Conversation/app/src/main/res/xml/backup_scheme.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/conversation/Conversation/build.gradle b/conversation/Conversation/build.gradle new file mode 100644 index 00000000..ad338d74 --- /dev/null +++ b/conversation/Conversation/build.gradle @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.3.3' + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.2' + } +} + +allprojects { + repositories { + jcenter() + google() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/conversation/Conversation/gradle.properties b/conversation/Conversation/gradle.properties new file mode 100644 index 00000000..422a2dea --- /dev/null +++ b/conversation/Conversation/gradle.properties @@ -0,0 +1,22 @@ +# Copyright 2017 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# Configuration for API.AI agent +apiAiProjectName=android-docs-samples +apiAiAgentName=android-docs-samples +apiAiLanguageCode=en-US diff --git a/conversation/Conversation/gradle/wrapper/gradle-wrapper.jar b/conversation/Conversation/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 GIT binary patch literal 53636 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ⅅ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 H-+%o-(+5aQ literal 0 HcmV?d00001 diff --git a/conversation/Conversation/gradle/wrapper/gradle-wrapper.properties b/conversation/Conversation/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..9d1b2924 --- /dev/null +++ b/conversation/Conversation/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jul 28 16:45:19 JST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip diff --git a/conversation/Conversation/gradlew b/conversation/Conversation/gradlew new file mode 100755 index 00000000..9d82f789 --- /dev/null +++ b/conversation/Conversation/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/conversation/Conversation/gradlew.bat b/conversation/Conversation/gradlew.bat new file mode 100644 index 00000000..aec99730 --- /dev/null +++ b/conversation/Conversation/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/conversation/Conversation/settings.gradle b/conversation/Conversation/settings.gradle new file mode 100644 index 00000000..e7b4def4 --- /dev/null +++ b/conversation/Conversation/settings.gradle @@ -0,0 +1 @@ +include ':app' From fb1ec82817e0f266169b5c98c14337a23c47fbcf Mon Sep 17 00:00:00 2001 From: Yuichi Araki Date: Fri, 13 Oct 2017 16:59:45 +0900 Subject: [PATCH 2/6] Rename to Dialogflow Bug: 65732552 Change-Id: I1e4ab601548f0254cb0799128e453602020fcde4 --- .../Dialogflow}/.gitignore | 0 .../Dialogflow}/README.md | 18 +++--- .../Dialogflow}/app/.gitignore | 0 .../Dialogflow}/app/build.gradle | 14 ++--- .../Dialogflow}/app/lint.xml | 0 .../Dialogflow}/app/proguard-rules.pro | 0 .../android/dialogflow}/MainActivityTest.java | 16 +++--- .../cloud/android/dialogflow}/TestUtils.java | 4 +- .../dialogflow/api/DialogflowServiceTest.java | 18 +++--- .../dialogflow}/api/UtteranceTest.java | 2 +- .../ui/AudioIndicatorViewTest.java | 4 +- .../dialogflow}/ui/BubbleViewTest.java | 4 +- .../dialogflow}/ui/InputHelperTest.java | 4 +- .../ui/MessageDialogFragmentTest.java | 2 +- .../cloud/android/dialogflow}/ui/UiTest.java | 2 +- .../app/src/debug/AndroidManifest.xml | 4 +- .../dialogflow}/ui/UiTestActivity.java | 4 +- .../res/layout/activity_conversation_test.xml | 4 +- .../app/src/main/AndroidManifest.xml | 8 +-- .../android/dialogflow}/MainActivity.java | 54 +++++++++--------- .../dialogflow/api/DialogflowService.java | 22 +++---- .../android/dialogflow}/api/Utterance.java | 2 +- .../dialogflow}/ui/AudioIndicatorView.java | 4 +- .../android/dialogflow}/ui/BubbleView.java | 4 +- .../android/dialogflow/ui/HistoryAdapter.java | 9 ++- .../android/dialogflow}/ui/InputHelper.java | 6 +- .../dialogflow}/ui/MessageDialogFragment.java | 2 +- .../dialogflow}/util/VoiceRecorder.java | 2 +- .../cloud/conversation/v1alpha/context.proto | 0 .../v1alpha/conversation_service.proto | 0 .../conversation/v1alpha/detect_intent.proto | 0 .../conversation/v1alpha/entity_type.proto | 0 .../cloud/conversation/v1alpha/intent.proto | 0 .../v1alpha/session_entity_type.proto | 0 .../src/main/res/drawable/bubble_incoming.xml | 0 .../src/main/res/drawable/bubble_outgoing.xml | 0 .../app/src/main/res/drawable/ic_keyboard.xml | 0 .../app/src/main/res/drawable/ic_mic.xml | 0 .../app/src/main/res/drawable/ic_send.xml | 0 .../app/src/main/res/layout/activity_main.xml | 2 +- .../src/main/res/layout/item_conversation.xml | 2 +- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../app/src/main/res/values/attrs.xml | 0 .../app/src/main/res/values/colors.xml | 0 .../app/src/main/res/values/dimens.xml | 0 .../app/src/main/res/values/strings.xml | 2 +- .../app/src/main/res/values/styles.xml | 2 +- .../app/src/main/res/xml/backup_scheme.xml | 0 .../Dialogflow}/build.gradle | 2 +- .../Dialogflow}/gradle.properties | 6 +- .../gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 .../Dialogflow}/gradlew | 0 .../Dialogflow}/gradlew.bat | 0 .../Dialogflow}/settings.gradle | 0 59 files changed, 114 insertions(+), 115 deletions(-) rename {conversation/Conversation => dialogflow/Dialogflow}/.gitignore (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/README.md (72%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/.gitignore (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/build.gradle (92%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/lint.xml (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/proguard-rules.pro (100%) rename {conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow}/MainActivityTest.java (86%) rename {conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow}/TestUtils.java (92%) rename conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/ConversationServiceTest.java => dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/api/DialogflowServiceTest.java (81%) rename {conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow}/api/UtteranceTest.java (97%) rename {conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow}/ui/AudioIndicatorViewTest.java (96%) rename {conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow}/ui/BubbleViewTest.java (94%) rename {conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow}/ui/InputHelperTest.java (97%) rename {conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow}/ui/MessageDialogFragmentTest.java (97%) rename {conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow}/ui/UiTest.java (95%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/debug/AndroidManifest.xml (87%) rename {conversation/Conversation/app/src/debug/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/debug/java/com/google/cloud/android/dialogflow}/ui/UiTestActivity.java (95%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/debug/res/layout/activity_conversation_test.xml (93%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/AndroidManifest.xml (84%) rename {conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow}/MainActivity.java (79%) rename conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/ConversationService.java => dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/api/DialogflowService.java (97%) rename {conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow}/api/Utterance.java (97%) rename {conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow}/ui/AudioIndicatorView.java (97%) rename {conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow}/ui/BubbleView.java (96%) rename conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/ConversationHistoryAdapter.java => dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/HistoryAdapter.java (90%) rename {conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow}/ui/InputHelper.java (98%) rename {conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow}/ui/MessageDialogFragment.java (98%) rename {conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation => dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow}/util/VoiceRecorder.java (99%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/proto/google/cloud/conversation/v1alpha/context.proto (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/proto/google/cloud/conversation/v1alpha/conversation_service.proto (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/proto/google/cloud/conversation/v1alpha/detect_intent.proto (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/proto/google/cloud/conversation/v1alpha/entity_type.proto (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/proto/google/cloud/conversation/v1alpha/intent.proto (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/proto/google/cloud/conversation/v1alpha/session_entity_type.proto (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/drawable/bubble_incoming.xml (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/drawable/bubble_outgoing.xml (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/drawable/ic_keyboard.xml (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/drawable/ic_mic.xml (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/drawable/ic_send.xml (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/layout/activity_main.xml (97%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/layout/item_conversation.xml (96%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/values/attrs.xml (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/values/colors.xml (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/values/dimens.xml (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/values/strings.xml (95%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/values/styles.xml (92%) rename {conversation/Conversation => dialogflow/Dialogflow}/app/src/main/res/xml/backup_scheme.xml (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/build.gradle (94%) rename {conversation/Conversation => dialogflow/Dialogflow}/gradle.properties (87%) rename {conversation/Conversation => dialogflow/Dialogflow}/gradle/wrapper/gradle-wrapper.jar (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/gradle/wrapper/gradle-wrapper.properties (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/gradlew (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/gradlew.bat (100%) rename {conversation/Conversation => dialogflow/Dialogflow}/settings.gradle (100%) diff --git a/conversation/Conversation/.gitignore b/dialogflow/Dialogflow/.gitignore similarity index 100% rename from conversation/Conversation/.gitignore rename to dialogflow/Dialogflow/.gitignore diff --git a/conversation/Conversation/README.md b/dialogflow/Dialogflow/README.md similarity index 72% rename from conversation/Conversation/README.md rename to dialogflow/Dialogflow/README.md index 473c8d2f..56b5d366 100644 --- a/conversation/Conversation/README.md +++ b/dialogflow/Dialogflow/README.md @@ -1,13 +1,13 @@ -# Google Cloud Conversation Engine examples +# Google Cloud Dialogflow Enterprise examples This directory contains Android example that uses the -[Google Cloud Conversation Engine](https://cloud.google.com/conversation/). +[Google Cloud Dialogflow Enterprise](https://cloud.google.com/conversation/). ## Prerequisites -### Enable Cloud Conversation Engine +### Enable Cloud Dialogflow Enterprise -If you have not already done so, [enable Cloud Conversation Engine for your project]( +If you have not already done so, [enable Cloud Dialogflow Enterprise for your project]( https://cloud.google.com/conversation/docs/quickstart). ### Set Up to Authenticate With Your Project's Credentials @@ -33,12 +33,12 @@ Again, ***you should not do this in your production app.*** See the [Cloud Platform Auth Guide](https://cloud.google.com/docs/authentication#developer_workflow) for more information. -### Project name and agent name for API.AI +### Project name and agent name for Dialogflow -Open the file `gradle.properties` and change the API.AI project name and agent name there. +Open the file `gradle.properties` and change the Dialogflow project name and agent name there. ``` -apiAiProjectName=(your project name here) -apiAiAgentName(your agent name here) -apiAiLanguageCode=(language code, such as en-US) +dialogflowProjectName=(your project name here) +dialogflowAgentName(your agent name here) +dialogflowLanguageCode=(language code, such as en-US) ``` diff --git a/conversation/Conversation/app/.gitignore b/dialogflow/Dialogflow/app/.gitignore similarity index 100% rename from conversation/Conversation/app/.gitignore rename to dialogflow/Dialogflow/app/.gitignore diff --git a/conversation/Conversation/app/build.gradle b/dialogflow/Dialogflow/app/build.gradle similarity index 92% rename from conversation/Conversation/app/build.gradle rename to dialogflow/Dialogflow/app/build.gradle index bb233eab..e37280f2 100644 --- a/conversation/Conversation/app/build.gradle +++ b/dialogflow/Dialogflow/app/build.gradle @@ -18,15 +18,15 @@ apply plugin: 'com.android.application' apply plugin: 'com.google.protobuf' ext { - supportLibraryVersion = '26.0.1' + supportLibraryVersion = '26.1.0' grpcVersion = '1.5.0' } android { compileSdkVersion 26 - buildToolsVersion '26.0.1' + buildToolsVersion '26.0.2' defaultConfig { - applicationId 'com.google.cloud.android.conversation' + applicationId 'com.google.cloud.android.dialogflow' minSdkVersion 16 targetSdkVersion 26 versionCode 1 @@ -34,10 +34,10 @@ android { testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner' vectorDrawables.useSupportLibrary = true - // API.AI project & agent settings - buildConfigField 'String', 'PROJECT_NAME', "\"${project.property("apiAiProjectName")}\"" - buildConfigField 'String', 'AGENT_NAME', "\"${project.property("apiAiAgentName")}\"" - buildConfigField 'String', 'LANGUAGE_CODE', "\"${project.property("apiAiLanguageCode")}\"" + // Dialogflow project & agent settings + buildConfigField 'String', 'PROJECT_NAME', "\"${project.property("dialogflowProjectName")}\"" + buildConfigField 'String', 'AGENT_NAME', "\"${project.property("dialogflowAgentName")}\"" + buildConfigField 'String', 'LANGUAGE_CODE', "\"${project.property("dialogflowLanguageCode")}\"" } buildTypes { debug { diff --git a/conversation/Conversation/app/lint.xml b/dialogflow/Dialogflow/app/lint.xml similarity index 100% rename from conversation/Conversation/app/lint.xml rename to dialogflow/Dialogflow/app/lint.xml diff --git a/conversation/Conversation/app/proguard-rules.pro b/dialogflow/Dialogflow/app/proguard-rules.pro similarity index 100% rename from conversation/Conversation/app/proguard-rules.pro rename to dialogflow/Dialogflow/app/proguard-rules.pro diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/MainActivityTest.java b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/MainActivityTest.java similarity index 86% rename from conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/MainActivityTest.java rename to dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/MainActivityTest.java index 69fde9c3..04da8957 100644 --- a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/MainActivityTest.java +++ b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/MainActivityTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation; +package com.google.cloud.android.dialogflow; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; @@ -25,7 +25,7 @@ import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.test.espresso.matcher.ViewMatchers.withText; -import static com.google.cloud.android.conversation.TestUtils.hasDirection; +import static com.google.cloud.android.dialogflow.TestUtils.hasDirection; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; @@ -40,8 +40,8 @@ import android.support.test.rule.GrantPermissionRule; import android.support.test.runner.AndroidJUnit4; -import com.google.cloud.android.conversation.api.ConversationService; -import com.google.cloud.android.conversation.api.Utterance; +import com.google.cloud.android.dialogflow.api.DialogflowService; +import com.google.cloud.android.dialogflow.api.Utterance; import org.junit.After; import org.junit.Before; @@ -61,18 +61,18 @@ public class MainActivityTest { public GrantPermissionRule permissionRule = GrantPermissionRule.grant(Manifest.permission.RECORD_AUDIO); - private ConversationService.Listener mListener; + private DialogflowService.Listener mListener; @Before public void setUp() { - final ConversationService service = activityRule.getActivity().mConversationService; - mListener = mock(ConversationService.Listener.class); + final DialogflowService service = activityRule.getActivity().mDialogflowService; + mListener = mock(DialogflowService.Listener.class); service.addListener(mListener); } @After public void tearDown() { - activityRule.getActivity().mConversationService.removeListener(mListener); + activityRule.getActivity().mDialogflowService.removeListener(mListener); } @Test diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/TestUtils.java b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/TestUtils.java similarity index 92% rename from conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/TestUtils.java rename to dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/TestUtils.java index 6e061af7..044df649 100644 --- a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/TestUtils.java +++ b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/TestUtils.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.google.cloud.android.conversation; +package com.google.cloud.android.dialogflow; -import com.google.cloud.android.conversation.api.Utterance; +import com.google.cloud.android.dialogflow.api.Utterance; import org.mockito.ArgumentMatcher; diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/ConversationServiceTest.java b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/api/DialogflowServiceTest.java similarity index 81% rename from conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/ConversationServiceTest.java rename to dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/api/DialogflowServiceTest.java index fe76519d..dbb5b931 100644 --- a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/ConversationServiceTest.java +++ b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/api/DialogflowServiceTest.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.api; +package com.google.cloud.android.dialogflow.api; -import static com.google.cloud.android.conversation.TestUtils.hasDirection; +import static com.google.cloud.android.dialogflow.TestUtils.hasDirection; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; @@ -30,7 +30,7 @@ import android.support.test.rule.ServiceTestRule; import android.support.test.runner.AndroidJUnit4; -import com.google.cloud.android.conversation.TestUtils; +import com.google.cloud.android.dialogflow.TestUtils; import org.junit.After; import org.junit.Before; @@ -42,19 +42,19 @@ @RunWith(AndroidJUnit4.class) -public class ConversationServiceTest { +public class DialogflowServiceTest { @Rule public final ServiceTestRule serviceRule = new ServiceTestRule(); - private ConversationService mService; - private ConversationService.Listener mListener; + private DialogflowService mService; + private DialogflowService.Listener mListener; @Before public void setUp() throws TimeoutException { - mService = ConversationService.from(serviceRule.bindService( - new Intent(InstrumentationRegistry.getTargetContext(), ConversationService.class))); - mListener = mock(ConversationService.Listener.class); + mService = DialogflowService.from(serviceRule.bindService( + new Intent(InstrumentationRegistry.getTargetContext(), DialogflowService.class))); + mListener = mock(DialogflowService.Listener.class); mService.addListener(mListener); } diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/UtteranceTest.java b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/api/UtteranceTest.java similarity index 97% rename from conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/UtteranceTest.java rename to dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/api/UtteranceTest.java index ba5fb1e2..71f17da0 100644 --- a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/api/UtteranceTest.java +++ b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/api/UtteranceTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.api; +package com.google.cloud.android.dialogflow.api; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/AudioIndicatorViewTest.java b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/AudioIndicatorViewTest.java similarity index 96% rename from conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/AudioIndicatorViewTest.java rename to dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/AudioIndicatorViewTest.java index 0eb6f7c9..8c5f98c2 100644 --- a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/AudioIndicatorViewTest.java +++ b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/AudioIndicatorViewTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.ui; +package com.google.cloud.android.dialogflow.ui; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; @@ -30,7 +30,7 @@ import android.support.test.runner.AndroidJUnit4; import android.view.View; -import com.google.cloud.android.conversation.R; +import com.google.cloud.android.dialogflow.R; import org.hamcrest.Matcher; import org.junit.Before; diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/BubbleViewTest.java b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/BubbleViewTest.java similarity index 94% rename from conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/BubbleViewTest.java rename to dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/BubbleViewTest.java index 8798e40e..ff199e2f 100644 --- a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/BubbleViewTest.java +++ b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/BubbleViewTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.ui; +package com.google.cloud.android.dialogflow.ui; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; @@ -23,7 +23,7 @@ import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; -import com.google.cloud.android.conversation.R; +import com.google.cloud.android.dialogflow.R; import org.junit.Before; import org.junit.Test; diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/InputHelperTest.java b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/InputHelperTest.java similarity index 97% rename from conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/InputHelperTest.java rename to dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/InputHelperTest.java index 14165977..c080e760 100644 --- a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/InputHelperTest.java +++ b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/InputHelperTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.ui; +package com.google.cloud.android.dialogflow.ui; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; @@ -36,7 +36,7 @@ import android.widget.EditText; import android.widget.ImageButton; -import com.google.cloud.android.conversation.R; +import com.google.cloud.android.dialogflow.R; import org.junit.After; import org.junit.Before; diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/MessageDialogFragmentTest.java b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/MessageDialogFragmentTest.java similarity index 97% rename from conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/MessageDialogFragmentTest.java rename to dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/MessageDialogFragmentTest.java index 894684b8..debc0c55 100644 --- a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/MessageDialogFragmentTest.java +++ b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/MessageDialogFragmentTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.ui; +package com.google.cloud.android.dialogflow.ui; import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.action.ViewActions.click; diff --git a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/UiTest.java b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/UiTest.java similarity index 95% rename from conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/UiTest.java rename to dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/UiTest.java index c05417e2..af3e67a9 100644 --- a/conversation/Conversation/app/src/androidTest/java/com/google/cloud/android/conversation/ui/UiTest.java +++ b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/UiTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.ui; +package com.google.cloud.android.dialogflow.ui; import android.Manifest; import android.support.test.rule.ActivityTestRule; diff --git a/conversation/Conversation/app/src/debug/AndroidManifest.xml b/dialogflow/Dialogflow/app/src/debug/AndroidManifest.xml similarity index 87% rename from conversation/Conversation/app/src/debug/AndroidManifest.xml rename to dialogflow/Dialogflow/app/src/debug/AndroidManifest.xml index ef2bc625..4b85bf00 100644 --- a/conversation/Conversation/app/src/debug/AndroidManifest.xml +++ b/dialogflow/Dialogflow/app/src/debug/AndroidManifest.xml @@ -17,11 +17,11 @@ + package="com.google.cloud.android.dialogflow"> diff --git a/conversation/Conversation/app/src/debug/java/com/google/cloud/android/conversation/ui/UiTestActivity.java b/dialogflow/Dialogflow/app/src/debug/java/com/google/cloud/android/dialogflow/ui/UiTestActivity.java similarity index 95% rename from conversation/Conversation/app/src/debug/java/com/google/cloud/android/conversation/ui/UiTestActivity.java rename to dialogflow/Dialogflow/app/src/debug/java/com/google/cloud/android/dialogflow/ui/UiTestActivity.java index dec2acb0..b44ae962 100644 --- a/conversation/Conversation/app/src/debug/java/com/google/cloud/android/conversation/ui/UiTestActivity.java +++ b/dialogflow/Dialogflow/app/src/debug/java/com/google/cloud/android/dialogflow/ui/UiTestActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.ui; +package com.google.cloud.android.dialogflow.ui; import android.Manifest; import android.content.pm.PackageManager; @@ -23,7 +23,7 @@ import android.support.v4.app.ActivityCompat; import android.support.v7.app.AppCompatActivity; -import com.google.cloud.android.conversation.R; +import com.google.cloud.android.dialogflow.R; /** diff --git a/conversation/Conversation/app/src/debug/res/layout/activity_conversation_test.xml b/dialogflow/Dialogflow/app/src/debug/res/layout/activity_conversation_test.xml similarity index 93% rename from conversation/Conversation/app/src/debug/res/layout/activity_conversation_test.xml rename to dialogflow/Dialogflow/app/src/debug/res/layout/activity_conversation_test.xml index ca666092..8912ad87 100644 --- a/conversation/Conversation/app/src/debug/res/layout/activity_conversation_test.xml +++ b/dialogflow/Dialogflow/app/src/debug/res/layout/activity_conversation_test.xml @@ -21,7 +21,7 @@ android:layout_height="match_parent" android:orientation="vertical"> - - + package="com.google.cloud.android.dialogflow"> @@ -28,17 +28,17 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/Theme.Conversation" + android:theme="@style/Theme.Dialogflow" tools:ignore="GoogleAppIndexingWarning"> - + diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/MainActivity.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/MainActivity.java similarity index 79% rename from conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/MainActivity.java rename to dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/MainActivity.java index 3e48f422..da940421 100644 --- a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/MainActivity.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/MainActivity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation; +package com.google.cloud.android.dialogflow; import android.Manifest; import android.content.ComponentName; @@ -33,12 +33,12 @@ import android.widget.EditText; import android.widget.ImageButton; -import com.google.cloud.android.conversation.api.ConversationService; -import com.google.cloud.android.conversation.api.Utterance; -import com.google.cloud.android.conversation.ui.AudioIndicatorView; -import com.google.cloud.android.conversation.ui.ConversationHistoryAdapter; -import com.google.cloud.android.conversation.ui.InputHelper; -import com.google.cloud.android.conversation.ui.MessageDialogFragment; +import com.google.cloud.android.dialogflow.api.DialogflowService; +import com.google.cloud.android.dialogflow.api.Utterance; +import com.google.cloud.android.dialogflow.ui.AudioIndicatorView; +import com.google.cloud.android.dialogflow.ui.HistoryAdapter; +import com.google.cloud.android.dialogflow.ui.InputHelper; +import com.google.cloud.android.dialogflow.ui.MessageDialogFragment; public class MainActivity extends AppCompatActivity { @@ -49,10 +49,10 @@ public class MainActivity extends AppCompatActivity { private static final String STATE_HISTORY = "history"; @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - ConversationService mConversationService; + DialogflowService mDialogflowService; private LinearLayoutManager mLayoutManager; - private ConversationHistoryAdapter mAdapter; + private HistoryAdapter mAdapter; private InputHelper mInputHelper; @@ -60,19 +60,19 @@ public class MainActivity extends AppCompatActivity { @Override public void onServiceConnected(ComponentName componentName, IBinder binder) { - mConversationService = ConversationService.from(binder); - mConversationService.addListener(mSpeechServiceListener); + mDialogflowService = DialogflowService.from(binder); + mDialogflowService.addListener(mSpeechServiceListener); } @Override public void onServiceDisconnected(ComponentName componentName) { - mConversationService = null; + mDialogflowService = null; } }; - private ConversationService.Listener mSpeechServiceListener - = new ConversationService.Listener() { + private DialogflowService.Listener mSpeechServiceListener + = new DialogflowService.Listener() { @Override public void onApiReady() { @@ -107,7 +107,7 @@ protected void onCreate(Bundle savedInstanceState) { mLayoutManager = new LinearLayoutManager(this); mLayoutManager.setStackFromEnd(true); history.setLayoutManager(mLayoutManager); - mAdapter = new ConversationHistoryAdapter(); + mAdapter = new HistoryAdapter(); if (savedInstanceState != null) { mAdapter.restoreHistory( savedInstanceState.getParcelableArrayList(STATE_HISTORY)); @@ -119,29 +119,29 @@ protected void onCreate(Bundle savedInstanceState) { (AudioIndicatorView) findViewById(R.id.indicator), new InputHelper.Callback() { @Override public void onText(String text) { - if (mConversationService != null) { - mConversationService.detectIntentByText(text); + if (mDialogflowService != null) { + mDialogflowService.detectIntentByText(text); } } @Override public void onVoiceStart() { - if (mConversationService != null) { - mConversationService.startDetectIntentByVoice(mInputHelper.getSampleRate()); + if (mDialogflowService != null) { + mDialogflowService.startDetectIntentByVoice(mInputHelper.getSampleRate()); } } @Override public void onVoice(byte[] data, int size) { - if (mConversationService != null) { - mConversationService.detectIntentByVoice(data, size); + if (mDialogflowService != null) { + mDialogflowService.detectIntentByVoice(data, size); } } @Override public void onVoiceEnd() { - if (mConversationService != null) { - mConversationService.finishDetectIntentByVoice(); + if (mDialogflowService != null) { + mDialogflowService.finishDetectIntentByVoice(); } } @@ -174,17 +174,17 @@ protected void onStart() { super.onStart(); // Prepare Cloud Conversation Engine - bindService(new Intent(this, ConversationService.class), mServiceConnection, + bindService(new Intent(this, DialogflowService.class), mServiceConnection, BIND_AUTO_CREATE); } @Override protected void onStop() { // Stop Cloud Conversation Engine - if (mConversationService != null) { - mConversationService.removeListener(mSpeechServiceListener); + if (mDialogflowService != null) { + mDialogflowService.removeListener(mSpeechServiceListener); unbindService(mServiceConnection); - mConversationService = null; + mDialogflowService = null; } super.onStop(); diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/ConversationService.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/api/DialogflowService.java similarity index 97% rename from conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/ConversationService.java rename to dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/api/DialogflowService.java index 3e84485c..57c3e8d4 100644 --- a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/ConversationService.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/api/DialogflowService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.api; +package com.google.cloud.android.dialogflow.api; import android.app.Service; import android.content.Context; @@ -31,8 +31,8 @@ import com.google.auth.Credentials; import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; -import com.google.cloud.android.conversation.BuildConfig; -import com.google.cloud.android.conversation.R; +import com.google.cloud.android.dialogflow.BuildConfig; +import com.google.cloud.android.dialogflow.R; import com.google.cloud.conversation.v1alpha.AudioEncoding; import com.google.cloud.conversation.v1alpha.ConversationServiceGrpc; import com.google.cloud.conversation.v1alpha.DetectIntentRequest; @@ -78,12 +78,12 @@ /** - * This service interacts with Cloud Conversation Engine. + * This service interacts with Cloud Dialogflow Enterprise. */ -public class ConversationService extends Service { +public class DialogflowService extends Service { /** - * Callback listener for {@link ConversationService}. + * Callback listener for {@link DialogflowService}. */ public interface Listener { @@ -108,9 +108,9 @@ public interface Listener { } - private static final String TAG = "ConversationService"; + private static final String TAG = "DialogflowService"; - private static final String PREFS = "ConversationService"; + private static final String PREFS = "DialogflowService"; private static final String PREF_ACCESS_TOKEN_VALUE = "access_token_value"; private static final String PREF_ACCESS_TOKEN_EXPIRATION_TIME = "access_token_expiration_time"; @@ -227,7 +227,7 @@ public void onCompleted() { private StreamObserver mRequestObserver; - public static ConversationService from(IBinder binder) { + public static DialogflowService from(IBinder binder) { return ((ConversationBinder) binder).getService(); } @@ -366,8 +366,8 @@ private String createSessionName(String project, String agent, int id) { private class ConversationBinder extends Binder { - ConversationService getService() { - return ConversationService.this; + DialogflowService getService() { + return DialogflowService.this; } } diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/Utterance.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/api/Utterance.java similarity index 97% rename from conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/Utterance.java rename to dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/api/Utterance.java index 85c01c16..5efe3148 100644 --- a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/api/Utterance.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/api/Utterance.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.api; +package com.google.cloud.android.dialogflow.api; import android.os.Parcel; import android.os.Parcelable; diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/AudioIndicatorView.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/AudioIndicatorView.java similarity index 97% rename from conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/AudioIndicatorView.java rename to dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/AudioIndicatorView.java index d5560754..bfe74063 100644 --- a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/AudioIndicatorView.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/AudioIndicatorView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.ui; +package com.google.cloud.android.dialogflow.ui; import android.animation.ObjectAnimator; import android.content.Context; @@ -27,7 +27,7 @@ import android.view.View; import android.view.animation.AccelerateDecelerateInterpolator; -import com.google.cloud.android.conversation.R; +import com.google.cloud.android.dialogflow.R; /** diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/BubbleView.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/BubbleView.java similarity index 96% rename from conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/BubbleView.java rename to dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/BubbleView.java index 3a4c9a6c..4a0454db 100644 --- a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/BubbleView.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/BubbleView.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.ui; +package com.google.cloud.android.dialogflow.ui; import android.content.Context; import android.content.res.ColorStateList; @@ -26,7 +26,7 @@ import android.support.v7.widget.AppCompatTextView; import android.util.AttributeSet; -import com.google.cloud.android.conversation.R; +import com.google.cloud.android.dialogflow.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/ConversationHistoryAdapter.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/HistoryAdapter.java similarity index 90% rename from conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/ConversationHistoryAdapter.java rename to dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/HistoryAdapter.java index 03f39f96..935d7d9f 100644 --- a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/ConversationHistoryAdapter.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/HistoryAdapter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.ui; +package com.google.cloud.android.dialogflow.ui; import android.support.v7.widget.RecyclerView; import android.view.Gravity; @@ -22,14 +22,13 @@ import android.view.ViewGroup; import android.widget.FrameLayout; -import com.google.cloud.android.conversation.R; -import com.google.cloud.android.conversation.api.Utterance; +import com.google.cloud.android.dialogflow.R; +import com.google.cloud.android.dialogflow.api.Utterance; import java.util.ArrayList; -public class ConversationHistoryAdapter extends - RecyclerView.Adapter { +public class HistoryAdapter extends RecyclerView.Adapter { private final ArrayList mHistory = new ArrayList<>(); diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/InputHelper.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/InputHelper.java similarity index 98% rename from conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/InputHelper.java rename to dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/InputHelper.java index d54dea38..a4e3bde1 100644 --- a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/InputHelper.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/InputHelper.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.ui; +package com.google.cloud.android.dialogflow.ui; import android.content.Context; import android.support.annotation.Nullable; @@ -27,8 +27,8 @@ import android.widget.ImageButton; import android.widget.TextView; -import com.google.cloud.android.conversation.R; -import com.google.cloud.android.conversation.util.VoiceRecorder; +import com.google.cloud.android.dialogflow.R; +import com.google.cloud.android.dialogflow.util.VoiceRecorder; /** diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/MessageDialogFragment.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/MessageDialogFragment.java similarity index 98% rename from conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/MessageDialogFragment.java rename to dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/MessageDialogFragment.java index 0cd15a1f..e697eeda 100644 --- a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/ui/MessageDialogFragment.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/MessageDialogFragment.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.ui; +package com.google.cloud.android.dialogflow.ui; import android.app.Dialog; import android.content.DialogInterface; diff --git a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/util/VoiceRecorder.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/util/VoiceRecorder.java similarity index 99% rename from conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/util/VoiceRecorder.java rename to dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/util/VoiceRecorder.java index 691b46fa..06bcf6c1 100644 --- a/conversation/Conversation/app/src/main/java/com/google/cloud/android/conversation/util/VoiceRecorder.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/util/VoiceRecorder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.android.conversation.util; +package com.google.cloud.android.dialogflow.util; import android.media.AudioFormat; import android.media.AudioRecord; diff --git a/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/context.proto b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/conversation/v1alpha/context.proto similarity index 100% rename from conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/context.proto rename to dialogflow/Dialogflow/app/src/main/proto/google/cloud/conversation/v1alpha/context.proto diff --git a/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/conversation_service.proto b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/conversation/v1alpha/conversation_service.proto similarity index 100% rename from conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/conversation_service.proto rename to dialogflow/Dialogflow/app/src/main/proto/google/cloud/conversation/v1alpha/conversation_service.proto diff --git a/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/detect_intent.proto b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/conversation/v1alpha/detect_intent.proto similarity index 100% rename from conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/detect_intent.proto rename to dialogflow/Dialogflow/app/src/main/proto/google/cloud/conversation/v1alpha/detect_intent.proto diff --git a/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/entity_type.proto b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/conversation/v1alpha/entity_type.proto similarity index 100% rename from conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/entity_type.proto rename to dialogflow/Dialogflow/app/src/main/proto/google/cloud/conversation/v1alpha/entity_type.proto diff --git a/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/intent.proto b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/conversation/v1alpha/intent.proto similarity index 100% rename from conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/intent.proto rename to dialogflow/Dialogflow/app/src/main/proto/google/cloud/conversation/v1alpha/intent.proto diff --git a/conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/session_entity_type.proto b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/conversation/v1alpha/session_entity_type.proto similarity index 100% rename from conversation/Conversation/app/src/main/proto/google/cloud/conversation/v1alpha/session_entity_type.proto rename to dialogflow/Dialogflow/app/src/main/proto/google/cloud/conversation/v1alpha/session_entity_type.proto diff --git a/conversation/Conversation/app/src/main/res/drawable/bubble_incoming.xml b/dialogflow/Dialogflow/app/src/main/res/drawable/bubble_incoming.xml similarity index 100% rename from conversation/Conversation/app/src/main/res/drawable/bubble_incoming.xml rename to dialogflow/Dialogflow/app/src/main/res/drawable/bubble_incoming.xml diff --git a/conversation/Conversation/app/src/main/res/drawable/bubble_outgoing.xml b/dialogflow/Dialogflow/app/src/main/res/drawable/bubble_outgoing.xml similarity index 100% rename from conversation/Conversation/app/src/main/res/drawable/bubble_outgoing.xml rename to dialogflow/Dialogflow/app/src/main/res/drawable/bubble_outgoing.xml diff --git a/conversation/Conversation/app/src/main/res/drawable/ic_keyboard.xml b/dialogflow/Dialogflow/app/src/main/res/drawable/ic_keyboard.xml similarity index 100% rename from conversation/Conversation/app/src/main/res/drawable/ic_keyboard.xml rename to dialogflow/Dialogflow/app/src/main/res/drawable/ic_keyboard.xml diff --git a/conversation/Conversation/app/src/main/res/drawable/ic_mic.xml b/dialogflow/Dialogflow/app/src/main/res/drawable/ic_mic.xml similarity index 100% rename from conversation/Conversation/app/src/main/res/drawable/ic_mic.xml rename to dialogflow/Dialogflow/app/src/main/res/drawable/ic_mic.xml diff --git a/conversation/Conversation/app/src/main/res/drawable/ic_send.xml b/dialogflow/Dialogflow/app/src/main/res/drawable/ic_send.xml similarity index 100% rename from conversation/Conversation/app/src/main/res/drawable/ic_send.xml rename to dialogflow/Dialogflow/app/src/main/res/drawable/ic_send.xml diff --git a/conversation/Conversation/app/src/main/res/layout/activity_main.xml b/dialogflow/Dialogflow/app/src/main/res/layout/activity_main.xml similarity index 97% rename from conversation/Conversation/app/src/main/res/layout/activity_main.xml rename to dialogflow/Dialogflow/app/src/main/res/layout/activity_main.xml index 548bfa4c..265abe45 100644 --- a/conversation/Conversation/app/src/main/res/layout/activity_main.xml +++ b/dialogflow/Dialogflow/app/src/main/res/layout/activity_main.xml @@ -58,7 +58,7 @@ android:maxLines="1" android:padding="@dimen/spacing_large"/> - - - Conversation + Dialogflow Write a message Speak This app needs to record audio and recognize your speech. diff --git a/conversation/Conversation/app/src/main/res/values/styles.xml b/dialogflow/Dialogflow/app/src/main/res/values/styles.xml similarity index 92% rename from conversation/Conversation/app/src/main/res/values/styles.xml rename to dialogflow/Dialogflow/app/src/main/res/values/styles.xml index a7df0582..3e678a6e 100644 --- a/conversation/Conversation/app/src/main/res/values/styles.xml +++ b/dialogflow/Dialogflow/app/src/main/res/values/styles.xml @@ -16,7 +16,7 @@ --> - - - diff --git a/dialogflow/Dialogflow/gradle.properties b/dialogflow/Dialogflow/gradle.properties index 41c906cf..d2fc6cf1 100644 --- a/dialogflow/Dialogflow/gradle.properties +++ b/dialogflow/Dialogflow/gradle.properties @@ -18,5 +18,4 @@ org.gradle.jvmargs=-Xmx1536m # Configuration for API.AI agent dialogflowProjectName=android-docs-samples -dialogflowAgentName=android-docs-samples dialogflowLanguageCode=en-US From f6f63b8a17c3995a36bff142dd5c14050da81d8d Mon Sep 17 00:00:00 2001 From: Yuichi Araki Date: Wed, 15 Nov 2017 16:40:36 +0900 Subject: [PATCH 4/6] Fix several issues Speech bubbles had incorrect paddings on older devices. Prevent audio indicator from covering speech feedback. Bug: 65732552 Change-Id: Id0839be59e7b92c919913949eb4fa97cf8730eed --- .../dialogflow/ui/InputHelperTest.java | 2 +- .../src/debug/res/layout/activity_ui_test.xml | 2 +- .../android/dialogflow/ui/BubbleView.java | 19 ++++++++- .../android/dialogflow/ui/InputHelper.java | 2 +- .../src/main/res/drawable/bubble_incoming.xml | 6 +-- .../src/main/res/drawable/bubble_outgoing.xml | 6 +-- .../app/src/main/res/layout/activity_main.xml | 41 ++++++++----------- .../app/src/main/res/values/dimens.xml | 4 ++ 8 files changed, 44 insertions(+), 38 deletions(-) diff --git a/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/InputHelperTest.java b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/InputHelperTest.java index c080e760..d29933ed 100644 --- a/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/InputHelperTest.java +++ b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/InputHelperTest.java @@ -84,7 +84,7 @@ public void initialState() { assertThat(mEditText.isEnabled(), is(true)); assertThat(mToggle.isEnabled(), is(true)); assertThat(mToggle.getVisibility(), is(View.VISIBLE)); - assertThat(mAudioIndicatorView.getVisibility(), is(View.INVISIBLE)); + assertThat(mAudioIndicatorView.getVisibility(), is(View.GONE)); } @Test diff --git a/dialogflow/Dialogflow/app/src/debug/res/layout/activity_ui_test.xml b/dialogflow/Dialogflow/app/src/debug/res/layout/activity_ui_test.xml index 8912ad87..029709a2 100644 --- a/dialogflow/Dialogflow/app/src/debug/res/layout/activity_ui_test.xml +++ b/dialogflow/Dialogflow/app/src/debug/res/layout/activity_ui_test.xml @@ -45,6 +45,6 @@ android:id="@+id/indicator" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:visibility="invisible"/> + android:visibility="gone"/> diff --git a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/BubbleView.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/BubbleView.java index 4a0454db..dacb6527 100644 --- a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/BubbleView.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/BubbleView.java @@ -18,6 +18,7 @@ import android.content.Context; import android.content.res.ColorStateList; +import android.content.res.Resources; import android.content.res.TypedArray; import android.support.annotation.IntDef; import android.support.annotation.Nullable; @@ -40,8 +41,12 @@ public class BubbleView extends AppCompatTextView { public static final int DIRECTION_INCOMING = 1; public static final int DIRECTION_OUTGOING = 2; - private ColorStateList mTintIncoming; - private ColorStateList mTintOutgoing; + private final ColorStateList mTintIncoming; + private final ColorStateList mTintOutgoing; + + private final int mPaddingVertical; + private final int mPaddingHorizontalShort; + private final int mPaddingHorizontalLong; @IntDef({DIRECTION_INCOMING, DIRECTION_OUTGOING}) @Retention(RetentionPolicy.SOURCE) @@ -65,6 +70,12 @@ public BubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAtt ContextCompat.getColor(context, R.color.incoming)); mTintOutgoing = ColorStateList.valueOf( ContextCompat.getColor(context, R.color.outgoing)); + final Resources resources = getResources(); + mPaddingVertical = resources.getDimensionPixelSize(R.dimen.bubble_padding_vertical); + mPaddingHorizontalShort = resources.getDimensionPixelSize( + R.dimen.bubble_padding_horizontal_short); + mPaddingHorizontalLong = resources.getDimensionPixelSize( + R.dimen.bubble_padding_horizontal_long); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BubbleView); setDirection(a.getInt(R.styleable.BubbleView_direction, DIRECTION_INCOMING)); a.recycle(); @@ -78,9 +89,13 @@ public void setDirection(@Direction int direction) { if (mDirection == DIRECTION_INCOMING) { setBackgroundResource(R.drawable.bubble_incoming); ViewCompat.setBackgroundTintList(this, mTintIncoming); + setPadding(mPaddingHorizontalLong, mPaddingVertical, + mPaddingHorizontalShort, mPaddingVertical); } else { setBackgroundResource(R.drawable.bubble_outgoing); ViewCompat.setBackgroundTintList(this, mTintOutgoing); + setPadding(mPaddingHorizontalShort, mPaddingVertical, + mPaddingHorizontalLong, mPaddingVertical); } } diff --git a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/InputHelper.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/InputHelper.java index a4e3bde1..c66512b3 100644 --- a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/InputHelper.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/InputHelper.java @@ -179,7 +179,7 @@ private void startAudio() { private void startText() { mVoiceRecorder.stop(); - mIndicator.setVisibility(View.INVISIBLE); + mIndicator.setVisibility(View.GONE); mText.getText().clear(); mText.setEnabled(true); mText.setHint(R.string.hint_text); diff --git a/dialogflow/Dialogflow/app/src/main/res/drawable/bubble_incoming.xml b/dialogflow/Dialogflow/app/src/main/res/drawable/bubble_incoming.xml index 23ecf580..3d8f0a1d 100644 --- a/dialogflow/Dialogflow/app/src/main/res/drawable/bubble_incoming.xml +++ b/dialogflow/Dialogflow/app/src/main/res/drawable/bubble_incoming.xml @@ -15,11 +15,7 @@ limitations under the License. --> + xmlns:android="http://schemas.android.com/apk/res/android"> + xmlns:android="http://schemas.android.com/apk/res/android"> - + + - - - - - - + android:layout_weight="1" + android:background="@null" + android:hint="@string/hint_text" + android:imeOptions="actionSend" + android:inputType="textCapSentences" + android:maxLines="1" + android:padding="@dimen/spacing_large"/> 4dp 48dp 64dp + + 16dp + 16dp + 24dp From 9fff72d7b5a74c185440e9ef593606255d9217ae Mon Sep 17 00:00:00 2001 From: Yuichi Araki Date: Thu, 23 Aug 2018 10:23:28 +0900 Subject: [PATCH 5/6] Update dependencies Bug: 112809915 Change-Id: I1ba68f651373ae9ce079695dd9c81825cd443254 --- dialogflow/Dialogflow/app/build.gradle | 13 +- .../dialogflow/ui/AudioIndicatorViewTest.java | 2 +- .../android/dialogflow/MainActivity.java | 2 +- .../dialogflow/api/DialogflowService.java | 22 +- .../android/dialogflow/ui/HistoryAdapter.java | 5 +- .../dialogflow/ui/MessageDialogFragment.java | 12 +- .../dialogflow/util/VoiceRecorder.java | 1 + .../dialogflow/{v2beta1 => v2}/agent.proto | 165 +++++++++---- .../dialogflow/{v2beta1 => v2}/context.proto | 87 ++++--- .../{v2beta1 => v2}/dialogflow_gapic.yaml | 66 ++++-- .../{v2beta1 => v2}/entity_type.proto | 169 ++++++++----- .../dialogflow/{v2beta1 => v2}/intent.proto | 223 +++++++++++++----- .../dialogflow/{v2beta1 => v2}/session.proto | 111 ++++++--- .../{v2beta1 => v2}/session_entity_type.proto | 65 +++-- .../dialogflow/{v2beta1 => v2}/webhook.proto | 49 +++- dialogflow/Dialogflow/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- 17 files changed, 685 insertions(+), 313 deletions(-) rename dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/{v2beta1 => v2}/agent.proto (51%) rename dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/{v2beta1 => v2}/context.proto (57%) rename dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/{v2beta1 => v2}/dialogflow_gapic.yaml (95%) rename dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/{v2beta1 => v2}/entity_type.proto (62%) rename dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/{v2beta1 => v2}/intent.proto (71%) rename dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/{v2beta1 => v2}/session.proto (80%) rename dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/{v2beta1 => v2}/session_entity_type.proto (72%) rename dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/{v2beta1 => v2}/webhook.proto (65%) diff --git a/dialogflow/Dialogflow/app/build.gradle b/dialogflow/Dialogflow/app/build.gradle index 8292e388..86de66f0 100644 --- a/dialogflow/Dialogflow/app/build.gradle +++ b/dialogflow/Dialogflow/app/build.gradle @@ -18,8 +18,8 @@ apply plugin: 'com.android.application' apply plugin: 'com.google.protobuf' ext { - supportLibraryVersion = '27.0.0' - grpcVersion = '1.7.0' + supportLibraryVersion = '27.1.1' + grpcVersion = '1.14.0' } android { @@ -101,10 +101,11 @@ dependencies { // Test testImplementation 'junit:junit:4.12' - testImplementation 'org.mockito:mockito-core:2.11.0' - androidTestImplementation 'org.mockito:mockito-android:2.11.0' - androidTestImplementation 'com.android.support.test:runner:1.0.1' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' + testImplementation 'org.mockito:mockito-core:2.19.0' + androidTestImplementation 'org.mockito:mockito-android:2.19.0' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test:rules:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' } task copySecretKey(type: Copy) { diff --git a/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/AudioIndicatorViewTest.java b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/AudioIndicatorViewTest.java index 8c5f98c2..abaf4aab 100644 --- a/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/AudioIndicatorViewTest.java +++ b/dialogflow/Dialogflow/app/src/androidTest/java/com/google/cloud/android/dialogflow/ui/AudioIndicatorViewTest.java @@ -57,7 +57,7 @@ public void run() { @Test @MediumTest - public void hearingVoice() throws Throwable { + public void hearingVoice() { assertThat(mAudioIndicatorView.isHearingVoice(), is(false)); onView(withId(R.id.indicator)).perform(setHearingVoice(true)); assertThat(mAudioIndicatorView.isHearingVoice(), is(true)); diff --git a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/MainActivity.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/MainActivity.java index da940421..56a189d3 100644 --- a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/MainActivity.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/MainActivity.java @@ -48,7 +48,7 @@ public class MainActivity extends AppCompatActivity { private static final String STATE_HISTORY = "history"; - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + @VisibleForTesting DialogflowService mDialogflowService; private LinearLayoutManager mLayoutManager; diff --git a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/api/DialogflowService.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/api/DialogflowService.java index 557f4c37..f8107ca5 100644 --- a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/api/DialogflowService.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/api/DialogflowService.java @@ -34,17 +34,17 @@ import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.android.dialogflow.BuildConfig; import com.google.cloud.android.dialogflow.R; -import com.google.cloud.dialogflow.v2beta1.AudioEncoding; -import com.google.cloud.dialogflow.v2beta1.DetectIntentRequest; -import com.google.cloud.dialogflow.v2beta1.DetectIntentResponse; -import com.google.cloud.dialogflow.v2beta1.InputAudioConfig; -import com.google.cloud.dialogflow.v2beta1.QueryInput; -import com.google.cloud.dialogflow.v2beta1.QueryResult; -import com.google.cloud.dialogflow.v2beta1.SessionsGrpc; -import com.google.cloud.dialogflow.v2beta1.StreamingDetectIntentRequest; -import com.google.cloud.dialogflow.v2beta1.StreamingDetectIntentResponse; -import com.google.cloud.dialogflow.v2beta1.StreamingRecognitionResult; -import com.google.cloud.dialogflow.v2beta1.TextInput; +import com.google.cloud.dialogflow.v2.AudioEncoding; +import com.google.cloud.dialogflow.v2.DetectIntentRequest; +import com.google.cloud.dialogflow.v2.DetectIntentResponse; +import com.google.cloud.dialogflow.v2.InputAudioConfig; +import com.google.cloud.dialogflow.v2.QueryInput; +import com.google.cloud.dialogflow.v2.QueryResult; +import com.google.cloud.dialogflow.v2.SessionsGrpc; +import com.google.cloud.dialogflow.v2.StreamingDetectIntentRequest; +import com.google.cloud.dialogflow.v2.StreamingDetectIntentResponse; +import com.google.cloud.dialogflow.v2.StreamingRecognitionResult; +import com.google.cloud.dialogflow.v2.TextInput; import com.google.protobuf.ByteString; import java.io.IOException; diff --git a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/HistoryAdapter.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/HistoryAdapter.java index 935d7d9f..e87c8ff0 100644 --- a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/HistoryAdapter.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/HistoryAdapter.java @@ -16,6 +16,7 @@ package com.google.cloud.android.dialogflow.ui; +import android.support.annotation.NonNull; import android.support.v7.widget.RecyclerView; import android.view.Gravity; import android.view.LayoutInflater; @@ -33,12 +34,12 @@ public class HistoryAdapter extends RecyclerView.Adapter mHistory = new ArrayList<>(); @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return new ViewHolder(LayoutInflater.from(parent.getContext()), parent); } @Override - public void onBindViewHolder(ViewHolder holder, int position) { + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { final Utterance utterance = mHistory.get(position); holder.bind(utterance.text, utterance.direction == Utterance.INCOMING diff --git a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/MessageDialogFragment.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/MessageDialogFragment.java index e697eeda..3ec14c48 100644 --- a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/MessageDialogFragment.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/ui/MessageDialogFragment.java @@ -58,18 +58,22 @@ public static MessageDialogFragment newInstance(String message) { @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - return new AlertDialog.Builder(getContext()) - .setMessage(getArguments().getString(ARG_MESSAGE)) + final AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()); + final Bundle args = getArguments(); + if (args != null) { + builder.setMessage(getArguments().getString(ARG_MESSAGE)); + } + return builder .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - ((Listener) getActivity()).onMessageDialogDismissed(); + ((Listener) requireActivity()).onMessageDialogDismissed(); } }) .setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialogInterface) { - ((Listener) getActivity()).onMessageDialogDismissed(); + ((Listener) requireActivity()).onMessageDialogDismissed(); } }) .create(); diff --git a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/util/VoiceRecorder.java b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/util/VoiceRecorder.java index 06bcf6c1..5f3ac9ff 100644 --- a/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/util/VoiceRecorder.java +++ b/dialogflow/Dialogflow/app/src/main/java/com/google/cloud/android/dialogflow/util/VoiceRecorder.java @@ -127,6 +127,7 @@ public void stop() { /** * Dismisses the currently ongoing utterance. */ + @SuppressWarnings("WeakerAccess") public void dismiss() { if (mLastVoiceHeardMillis != Long.MAX_VALUE) { mLastVoiceHeardMillis = Long.MAX_VALUE; diff --git a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/agent.proto b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/agent.proto similarity index 51% rename from dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/agent.proto rename to dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/agent.proto index 7869fb2f..ec2ed6da 100644 --- a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/agent.proto +++ b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/agent.proto @@ -1,4 +1,4 @@ -// Copyright 2017 Google Inc. +// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,33 +14,54 @@ syntax = "proto3"; -package google.cloud.dialogflow.v2beta1; +package google.cloud.dialogflow.v2; import "google/api/annotations.proto"; import "google/longrunning/operations.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/field_mask.proto"; +import "google/protobuf/struct.proto"; option cc_enable_arenas = true; -option csharp_namespace = "Google.Cloud.Dialogflow.V2beta1"; -option go_package = "google.golang.org/genproto/googleapis/cloud/dialogflow/v2beta1;dialogflow"; +option csharp_namespace = "Google.Cloud.Dialogflow.V2"; +option go_package = "google.golang.org/genproto/googleapis/cloud/dialogflow/v2;dialogflow"; option java_multiple_files = true; option java_outer_classname = "AgentProto"; -option java_package = "com.google.cloud.dialogflow.v2beta1"; +option java_package = "com.google.cloud.dialogflow.v2"; option objc_class_prefix = "DF"; -// Manages conversational agents. +// Agents are best described as Natural Language Understanding (NLU) modules +// that transform user requests into actionable data. You can include agents +// in your app, product, or service to determine user intent and respond to the +// user in a natural way. // +// After you create an agent, you can add [Intents][google.cloud.dialogflow.v2.Intents], [Contexts][google.cloud.dialogflow.v2.Contexts], +// [Entity Types][google.cloud.dialogflow.v2.EntityTypes], [Webhooks][google.cloud.dialogflow.v2.WebhookRequest], and so on to +// manage the flow of a conversation and match user input to predefined intents +// and actions. // -// Refer to [documentation](https://api.ai/docs/agents) for more details about -// # agents. +// You can create an agent using both Dialogflow Standard Edition and +// Dialogflow Enterprise Edition. For details, see +// [Dialogflow Editions](/dialogflow-enterprise/docs/editions). // -// Standard methods. +// You can save your agent for backup or versioning by exporting the agent by +// using the [ExportAgent][google.cloud.dialogflow.v2.Agents.ExportAgent] method. You can import a saved +// agent by using the [ImportAgent][google.cloud.dialogflow.v2.Agents.ImportAgent] method. +// +// Dialogflow provides several +// [prebuilt agents](https://dialogflow.com/docs/prebuilt-agents) for common +// conversation scenarios such as determining a date and time, converting +// currency, and so on. +// +// For more information about agents, see the +// [Dialogflow documentation](https://dialogflow.com/docs/agents). service Agents { // Retrieves the specified agent. rpc GetAgent(GetAgentRequest) returns (Agent) { - option (google.api.http) = { get: "/v2beta1/{parent=projects/*}/agent" }; + option (google.api.http) = { + get: "/v2/{parent=projects/*}/agent" + }; } // Returns the list of agents. @@ -51,23 +72,31 @@ service Agents { // Refer to [List // Sub-Collections](https://cloud.google.com/apis/design/design_patterns#list_sub-collections). rpc SearchAgents(SearchAgentsRequest) returns (SearchAgentsResponse) { - option (google.api.http) = { get: "/v2beta1/{parent=projects/*}/agent:search" }; + option (google.api.http) = { + get: "/v2/{parent=projects/*}/agent:search" + }; } // Trains the specified agent. // - // - // Operation + // Operation rpc TrainAgent(TrainAgentRequest) returns (google.longrunning.Operation) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*}/agent:train" body: "*" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*}/agent:train" + body: "*" + }; } // Exports the specified agent to a ZIP file. // - // - // Operation + // Operation rpc ExportAgent(ExportAgentRequest) returns (google.longrunning.Operation) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*}/agent:export" body: "*" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*}/agent:export" + body: "*" + }; } // Imports the specified agent from a ZIP file. @@ -76,10 +105,13 @@ service Agents { // Intents and entity types with the same name are replaced with the new // versions from ImportAgentRequest. // - // - // Operation + // Operation rpc ImportAgent(ImportAgentRequest) returns (google.longrunning.Operation) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*}/agent:import" body: "*" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*}/agent:import" + body: "*" + }; } // Restores the specified agent from a ZIP file. @@ -87,10 +119,13 @@ service Agents { // Replaces the current agent version with a new one. All the intents and // entity types in the older version are deleted. // - // - // Operation + // Operation rpc RestoreAgent(RestoreAgentRequest) returns (google.longrunning.Operation) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*}/agent:restore" body: "*" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*}/agent:restore" + body: "*" + }; } } @@ -118,7 +153,7 @@ message Agent { string display_name = 2; // Required. The default language of the agent as a language tag. See - // [Language Support](https://api.ai/docs/reference/language) for a + // [Language Support](https://dialogflow.com/docs/reference/language) for a // list of the currently supported language codes. // This field cannot be set by the `Update` method. string default_language_code = 3; @@ -137,8 +172,8 @@ message Agent { string description = 6; // Optional. The URI of the agent's avatar. - // Avatars are used throughout API.AI console and in the self-hosted - // [Web Demo](https://api.ai/docs/integrations/web-demo) integration. + // Avatars are used throughout the Dialogflow console and in the self-hosted + // [Web Demo](https://dialogflow.com/docs/integrations/web-demo) integration. string avatar_uri = 7; // Optional. Determines whether this agent should log conversation queries. @@ -157,14 +192,14 @@ message Agent { float classification_threshold = 10; } -// The request message for [Agents.GetAgent]. +// The request message for [Agents.GetAgent][google.cloud.dialogflow.v2.Agents.GetAgent]. message GetAgentRequest { - // Required. The name of the agent. + // Required. The project that the agent to fetch is associated with. // Format: `projects/`. string parent = 1; } -// The request message for [Agents.SearchAgents]. +// The request message for [Agents.SearchAgents][google.cloud.dialogflow.v2.Agents.SearchAgents]. message SearchAgentsRequest { // Required. The project to list agents from. // Format: `projects/`. @@ -178,7 +213,7 @@ message SearchAgentsRequest { string page_token = 3; } -// The response message for [Agents.SearchAgents]. +// The response message for [Agents.SearchAgents][google.cloud.dialogflow.v2.Agents.SearchAgents]. message SearchAgentsResponse { // The list of agents. There will be a maximum number of items returned based // on the page_size field in the request. @@ -189,67 +224,107 @@ message SearchAgentsResponse { string next_page_token = 2; } -// The request message for [Agents.TrainAgent]. +// The request message for [Agents.TrainAgent][google.cloud.dialogflow.v2.Agents.TrainAgent]. message TrainAgentRequest { - // Required. The name of the agent to train. + // Required. The project that the agent to train is associated with. // Format: `projects/`. string parent = 1; } -// The request message for [Agents.ExportAgent]. +// The request message for [Agents.ExportAgent][google.cloud.dialogflow.v2.Agents.ExportAgent]. message ExportAgentRequest { - // Required. The name of the agent to export. + // Required. The project that the agent to export is associated with. // Format: `projects/`. string parent = 1; - // Optional. The URI to export the agent to. Note: The URI must start with + // Optional. The Google Cloud Storage URI to export the agent to. + // Note: The URI must start with // "gs://". If left unspecified, the serialized agent is returned inline. string agent_uri = 2; } -// The response message for [Agents.ExportAgent]. +// The response message for [Agents.ExportAgent][google.cloud.dialogflow.v2.Agents.ExportAgent]. message ExportAgentResponse { // Required. The exported agent. oneof agent { // The URI to a file containing the exported agent. This field is populated - // only if `agent_uri` + // only if `agent_uri` is specified in `ExportAgentRequest`. string agent_uri = 1; // The exported agent. + // + // Example for how to export an agent to a zip file via a command line: + // + // curl \ + // 'https://dialogflow.googleapis.com/v2/projects//agent:export'\ + // -X POST \ + // -H 'Authorization: Bearer '$(gcloud auth print-access-token) \ + // -H 'Accept: application/json' \ + // -H 'Content-Type: application/json' \ + // --compressed \ + // --data-binary '{}' \ + // | grep agentContent | sed -e 's/.*"agentContent": "\([^"]*\)".*/\1/' \ + // | base64 --decode > bytes agent_content = 2; } } -// The request message for [Agents.ImportAgent]. +// The request message for [Agents.ImportAgent][google.cloud.dialogflow.v2.Agents.ImportAgent]. message ImportAgentRequest { - // Required. The name of the agent to import. + // Required. The project that the agent to import is associated with. // Format: `projects/`. string parent = 1; // Required. The agent to import. oneof agent { - // The URI to a file containing the agent to import. Note: The URI must - // start with "gs://". + // The URI to a Google Cloud Storage file containing the agent to import. + // Note: The URI must start with "gs://". string agent_uri = 2; // The agent to import. + // + // Example for how to import an agent via the command line: + // + // curl \ + // 'https://dialogflow.googleapis.com/v2/projects//agent:import\ + // -X POST \ + // -H 'Authorization: Bearer '$(gcloud auth print-access-token) \ + // -H 'Accept: application/json' \ + // -H 'Content-Type: application/json' \ + // --compressed \ + // --data-binary "{ + // 'agentContent': '$(cat | base64 -w 0)' + // }" bytes agent_content = 3; } } -// The request message for [Agents.RestoreAgent]. +// The request message for [Agents.RestoreAgent][google.cloud.dialogflow.v2.Agents.RestoreAgent]. message RestoreAgentRequest { - // Required. The name of the agent to restore. + // Required. The project that the agent to restore is associated with. // Format: `projects/`. string parent = 1; // Required. The agent to restore. oneof agent { - // The URI to a file containing the agent to restore. Note: The URI must - // start with "gs://". + // The URI to a Google Cloud Storage file containing the agent to restore. + // Note: The URI must start with "gs://". string agent_uri = 2; // The agent to restore. + // + // Example for how to restore an agent via the command line: + // + // curl \ + // 'https://dialogflow.googleapis.com/v2/projects//agent:restore\ + // -X POST \ + // -H 'Authorization: Bearer '$(gcloud auth print-access-token) \ + // -H 'Accept: application/json' \ + // -H 'Content-Type: application/json' \ + // --compressed \ + // --data-binary "{ + // 'agentContent': '$(cat | base64 -w 0)' + // }" \ bytes agent_content = 3; } } diff --git a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/context.proto b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/context.proto similarity index 57% rename from dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/context.proto rename to dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/context.proto index 08835ec9..2d1798e0 100644 --- a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/context.proto +++ b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/context.proto @@ -1,4 +1,4 @@ -// Copyright 2017 Google Inc. +// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ syntax = "proto3"; -package google.cloud.dialogflow.v2beta1; +package google.cloud.dialogflow.v2; import "google/api/annotations.proto"; import "google/protobuf/empty.proto"; @@ -22,50 +22,74 @@ import "google/protobuf/field_mask.proto"; import "google/protobuf/struct.proto"; option cc_enable_arenas = true; -option csharp_namespace = "Google.Cloud.Dialogflow.V2beta1"; -option go_package = "google.golang.org/genproto/googleapis/cloud/dialogflow/v2beta1;dialogflow"; +option csharp_namespace = "Google.Cloud.Dialogflow.V2"; +option go_package = "google.golang.org/genproto/googleapis/cloud/dialogflow/v2;dialogflow"; option java_multiple_files = true; option java_outer_classname = "ContextProto"; -option java_package = "com.google.cloud.dialogflow.v2beta1"; +option java_package = "com.google.cloud.dialogflow.v2"; option objc_class_prefix = "DF"; -// Manages contexts. +// A context represents additional information included with user input or with +// an intent returned by the Dialogflow API. Contexts are helpful for +// differentiating user input which may be vague or have a different meaning +// depending on additional details from your application such as user setting +// and preferences, previous user input, where the user is in your application, +// geographic location, and so on. // +// You can include contexts as input parameters of a +// [DetectIntent][google.cloud.dialogflow.v2.Sessions.DetectIntent] (or +// [StreamingDetectIntent][google.cloud.dialogflow.v2.Sessions.StreamingDetectIntent]) request, +// or as output contexts included in the returned intent. +// Contexts expire when an intent is matched, after the number of `DetectIntent` +// requests specified by the `lifespan_count` parameter, or after 10 minutes +// if no intents are matched for a `DetectIntent` request. // -// Refer to [documentation](https://api.ai/docs/contexts) for more details about -// # contexts. -// -// Standard methods. +// For more information about contexts, see the +// [Dialogflow documentation](https://dialogflow.com/docs/contexts). service Contexts { // Returns the list of all contexts in the specified session. rpc ListContexts(ListContextsRequest) returns (ListContextsResponse) { - option (google.api.http) = { get: "/v2beta1/{parent=projects/*/agent/sessions/*}/contexts" }; + option (google.api.http) = { + get: "/v2/{parent=projects/*/agent/sessions/*}/contexts" + }; } // Retrieves the specified context. rpc GetContext(GetContextRequest) returns (Context) { - option (google.api.http) = { get: "/v2beta1/{name=projects/*/agent/sessions/*/contexts/*}" }; + option (google.api.http) = { + get: "/v2/{name=projects/*/agent/sessions/*/contexts/*}" + }; } // Creates a context. rpc CreateContext(CreateContextRequest) returns (Context) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*/agent/sessions/*}/contexts" body: "context" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*/agent/sessions/*}/contexts" + body: "context" + }; } // Updates the specified context. rpc UpdateContext(UpdateContextRequest) returns (Context) { - option (google.api.http) = { patch: "/v2beta1/{context.name=projects/*/agent/sessions/*/contexts/*}" body: "context" }; + option (google.api.http) = { + patch: "/v2/{context.name=projects/*/agent/sessions/*/contexts/*}" + body: "context" + }; } // Deletes the specified context. rpc DeleteContext(DeleteContextRequest) returns (google.protobuf.Empty) { - option (google.api.http) = { delete: "/v2beta1/{name=projects/*/agent/sessions/*/contexts/*}" }; + option (google.api.http) = { + delete: "/v2/{name=projects/*/agent/sessions/*/contexts/*}" + }; } // Deletes all active contexts in the specified session. rpc DeleteAllContexts(DeleteAllContextsRequest) returns (google.protobuf.Empty) { - option (google.api.http) = { delete: "/v2beta1/{parent=projects/*/agent/sessions/*}/contexts" }; + option (google.api.http) = { + delete: "/v2/{parent=projects/*/agent/sessions/*}/contexts" + }; } } @@ -73,27 +97,21 @@ service Contexts { message Context { // Required. The unique identifier of the context. Format: // `projects//agent/sessions//contexts/`. - // Note: The Context ID is always converted to lowercase. string name = 1; // Optional. The number of conversational query requests after which the - // context expires. If set to `0` (the default) the context expires when an - // intent is detected for a query. Contexts expire automatically after - // 10 minutes even if there are no matching queries. - // If a context is added to a `DetectIntent` query - // (`QueryParameters.contexts`) - // and the `lifespan_count` for the context is less than or equal to `1`, - // the context can expire immediately if the `DetectIntent` query matches - // an intent. + // context expires. If set to `0` (the default) the context expires + // immediately. Contexts expire automatically after 10 minutes even if there + // are no matching queries. int32 lifespan_count = 2; // Optional. The collection of parameters associated with this context. - // Refer to [this doc](https://api.ai/docs/actions-and-parameters) for + // Refer to [this doc](https://dialogflow.com/docs/actions-and-parameters) for // syntax. google.protobuf.Struct parameters = 3; } -// The request message for [Contexts.ListContexts]. +// The request message for [Contexts.ListContexts][google.cloud.dialogflow.v2.Contexts.ListContexts]. message ListContextsRequest { // Required. The session to list all contexts from. // Format: `projects//agent/sessions/`. @@ -107,7 +125,7 @@ message ListContextsRequest { string page_token = 3; } -// The response message for [Contexts.ListContexts]. +// The response message for [Contexts.ListContexts][google.cloud.dialogflow.v2.Contexts.ListContexts]. message ListContextsResponse { // The list of contexts. There will be a maximum number of items // returned based on the page_size field in the request. @@ -118,14 +136,14 @@ message ListContextsResponse { string next_page_token = 2; } -// The request message for [Contexts.GetContext]. +// The request message for [Contexts.GetContext][google.cloud.dialogflow.v2.Contexts.GetContext]. message GetContextRequest { // Required. The name of the context. Format: // `projects//agent/sessions//contexts/`. string name = 1; } -// The request message for [Contexts.CreateContext]. +// The request message for [Contexts.CreateContext][google.cloud.dialogflow.v2.Contexts.CreateContext]. message CreateContextRequest { // Required. The session to create a context for. // Format: `projects//agent/sessions/`. @@ -135,24 +153,23 @@ message CreateContextRequest { Context context = 2; } -// The request message for [Contexts.UpdateContext]. +// The request message for [Contexts.UpdateContext][google.cloud.dialogflow.v2.Contexts.UpdateContext]. message UpdateContextRequest { - // Required. The context to update. Format: - // `projects//agent/sessions//contexts/`. + // Required. The context to update. Context context = 1; // Optional. The mask to control which fields get updated. google.protobuf.FieldMask update_mask = 2; } -// The request message for [Contexts.DeleteContext]. +// The request message for [Contexts.DeleteContext][google.cloud.dialogflow.v2.Contexts.DeleteContext]. message DeleteContextRequest { // Required. The name of the context to delete. Format: // `projects//agent/sessions//contexts/`. string name = 1; } -// The request message for [Contexts.DeleteAllContexts]. +// The request message for [Contexts.DeleteAllContexts][google.cloud.dialogflow.v2.Contexts.DeleteAllContexts]. message DeleteAllContextsRequest { // Required. The name of the session to delete all contexts from. Format: // `projects//agent/sessions/`. diff --git a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/dialogflow_gapic.yaml b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/dialogflow_gapic.yaml similarity index 95% rename from dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/dialogflow_gapic.yaml rename to dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/dialogflow_gapic.yaml index e3395a05..4664a8f9 100644 --- a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/dialogflow_gapic.yaml +++ b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/dialogflow_gapic.yaml @@ -3,20 +3,19 @@ config_schema_version: 1.0.0 # The settings of generated code in a specific language. language_settings: java: - package_name: com.google.cloud.dialogflow.v2beta1 + package_name: com.google.cloud.dialogflow.v2 python: - package_name: google.cloud.dialogflow_v2beta1.gapic + package_name: google.cloud.dialogflow_v2.gapic go: - package_name: cloud.google.com/go/cloud/dialogflow/apiv2beta1 + package_name: cloud.google.com/go/dialogflow/apiv2 csharp: - package_name: Google.Cloud.Dialogflow.V2beta1 + package_name: Google.Cloud.Dialogflow.V2 ruby: - package_name: Google::Cloud::Dialogflow::V2beta1 + package_name: Google::Cloud::Dialogflow::V2 php: - package_name: Google\Cloud\Dialogflow\V2beta1 + package_name: Google\Cloud\Dialogflow\V2 nodejs: - package_name: dialogflow.v2beta1 - domain_layer_location: google-cloud + package_name: dialogflow.v2 # The configuration for the license header to put on generated files. license_header: # The file containing the copyright line(s). @@ -26,7 +25,7 @@ license_header: # A list of API interface configurations. interfaces: # The fully qualified name of the API interface. -- name: google.cloud.dialogflow.v2beta1.Agents +- name: google.cloud.dialogflow.v2.Agents # A list of resource collection configurations. # Consists of a name_pattern and an entity_name. # The name_pattern is a pattern to describe the names of the resources of this @@ -40,6 +39,9 @@ interfaces: collections: - name_pattern: projects/{project} entity_name: project + language_overrides: + - language: csharp + common_resource_name: Google.Api.Gax.ResourceNames.ProjectName # Definition for retryable codes. retry_codes_def: - name: idempotent @@ -150,8 +152,10 @@ interfaces: parent: project timeout_millis: 60000 long_running: + # LINT.IfChange(train_agent_long_running_operation) return_type: google.protobuf.Empty metadata_type: google.protobuf.Struct + # LINT.ThenChange() initial_poll_delay_millis: 500 poll_delay_multiplier: 1.5 max_poll_delay_millis: 5000 @@ -171,8 +175,10 @@ interfaces: parent: project timeout_millis: 60000 long_running: - return_type: google.cloud.dialogflow.v2beta1.ExportAgentResponse + # LINT.IfChange(export_agent_long_running_operation) + return_type: google.cloud.dialogflow.v2.ExportAgentResponse metadata_type: google.protobuf.Struct + # LINT.ThenChange() initial_poll_delay_millis: 500 poll_delay_multiplier: 1.5 max_poll_delay_millis: 5000 @@ -188,8 +194,10 @@ interfaces: parent: project timeout_millis: 60000 long_running: + # LINT.IfChange(import_agent_long_running_operation) return_type: google.protobuf.Empty metadata_type: google.protobuf.Struct + # LINT.ThenChange() initial_poll_delay_millis: 500 poll_delay_multiplier: 1.5 max_poll_delay_millis: 5000 @@ -206,15 +214,17 @@ interfaces: parent: project timeout_millis: 60000 long_running: + # LINT.IfChange(restore_agent_long_running_operation) return_type: google.protobuf.Empty metadata_type: google.protobuf.Struct + # LINT.ThenChange() initial_poll_delay_millis: 500 poll_delay_multiplier: 1.5 max_poll_delay_millis: 5000 total_poll_timeout_millis: 300000 resource_name_treatment: STATIC_TYPES # The fully qualified name of the API interface. -- name: google.cloud.dialogflow.v2beta1.Contexts +- name: google.cloud.dialogflow.v2.Contexts # A list of resource collection configurations. # Consists of a name_pattern and an entity_name. # The name_pattern is a pattern to describe the names of the resources of this @@ -385,7 +395,7 @@ interfaces: timeout_millis: 60000 resource_name_treatment: STATIC_TYPES # The fully qualified name of the API interface. -- name: google.cloud.dialogflow.v2beta1.EntityTypes +- name: google.cloud.dialogflow.v2.EntityTypes # A list of resource collection configurations. # Consists of a name_pattern and an entity_name. # The name_pattern is a pattern to describe the names of the resources of this @@ -564,8 +574,10 @@ interfaces: parent: project_agent timeout_millis: 60000 long_running: - return_type: google.cloud.dialogflow.v2beta1.BatchUpdateEntityTypesResponse + # LINT.IfChange(batch_update_entity_types_long_running_operation) + return_type: google.cloud.dialogflow.v2.BatchUpdateEntityTypesResponse metadata_type: google.protobuf.Struct + # LINT.ThenChange() initial_poll_delay_millis: 500 poll_delay_multiplier: 1.5 max_poll_delay_millis: 5000 @@ -587,8 +599,10 @@ interfaces: parent: project_agent timeout_millis: 60000 long_running: + # LINT.IfChange(batch_delete_entity_types_long_running_operation) return_type: google.protobuf.Empty metadata_type: google.protobuf.Struct + # LINT.ThenChange() initial_poll_delay_millis: 500 poll_delay_multiplier: 1.5 max_poll_delay_millis: 5000 @@ -614,8 +628,10 @@ interfaces: parent: entity_type timeout_millis: 60000 long_running: + # LINT.IfChange(batch_create_entities_long_running_operation) return_type: google.protobuf.Empty metadata_type: google.protobuf.Struct + # LINT.ThenChange() initial_poll_delay_millis: 500 poll_delay_multiplier: 1.5 max_poll_delay_millis: 5000 @@ -641,8 +657,10 @@ interfaces: parent: entity_type timeout_millis: 60000 long_running: + # LINT.IfChange(batch_update_entities_long_running_operation) return_type: google.protobuf.Empty metadata_type: google.protobuf.Struct + # LINT.ThenChange() initial_poll_delay_millis: 500 poll_delay_multiplier: 1.5 max_poll_delay_millis: 5000 @@ -668,15 +686,17 @@ interfaces: parent: entity_type timeout_millis: 60000 long_running: + # LINT.IfChange(batch_delete_entities_long_running_operation) return_type: google.protobuf.Empty metadata_type: google.protobuf.Struct + # LINT.ThenChange() initial_poll_delay_millis: 500 poll_delay_multiplier: 1.5 max_poll_delay_millis: 5000 total_poll_timeout_millis: 300000 resource_name_treatment: STATIC_TYPES # The fully qualified name of the API interface. -- name: google.cloud.dialogflow.v2beta1.Intents +- name: google.cloud.dialogflow.v2.Intents # A list of resource collection configurations. # Consists of a name_pattern and an entity_name. # The name_pattern is a pattern to describe the names of the resources of this @@ -858,11 +878,13 @@ interfaces: retry_codes_name: non_idempotent retry_params_name: default field_name_patterns: - parent: agent + parent: project_agent timeout_millis: 60000 long_running: - return_type: google.cloud.dialogflow.v2beta1.BatchUpdateIntentsResponse + # LINT.IfChange(batch_update_intents_long_running_operation) + return_type: google.cloud.dialogflow.v2.BatchUpdateIntentsResponse metadata_type: google.protobuf.Struct + # LINT.ThenChange() initial_poll_delay_millis: 500 poll_delay_multiplier: 1.5 max_poll_delay_millis: 5000 @@ -881,18 +903,20 @@ interfaces: retry_codes_name: idempotent retry_params_name: default field_name_patterns: - parent: project + parent: project_agent timeout_millis: 60000 long_running: + # LINT.IfChange(batch_delete_intents_long_running_operation) return_type: google.protobuf.Empty metadata_type: google.protobuf.Struct + # LINT.ThenChange() initial_poll_delay_millis: 500 poll_delay_multiplier: 1.5 max_poll_delay_millis: 5000 total_poll_timeout_millis: 300000 resource_name_treatment: STATIC_TYPES # The fully qualified name of the API interface. -- name: google.cloud.dialogflow.v2beta1.SessionEntityTypes +- name: google.cloud.dialogflow.v2.SessionEntityTypes # A list of resource collection configurations. # Consists of a name_pattern and an entity_name. # The name_pattern is a pattern to describe the names of the resources of this @@ -1049,7 +1073,7 @@ interfaces: timeout_millis: 60000 resource_name_treatment: STATIC_TYPES # The fully qualified name of the API interface. -- name: google.cloud.dialogflow.v2beta1.Sessions +- name: google.cloud.dialogflow.v2.Sessions # A list of resource collection configurations. # Consists of a name_pattern and an entity_name. # The name_pattern is a pattern to describe the names of the resources of this @@ -1232,10 +1256,10 @@ resource_name_generation: name: intent - message_name: BatchUpdateIntentsRequest field_entity_map: - parent: agent + parent: project_agent - message_name: BatchDeleteIntentsRequest field_entity_map: - parent: project + parent: project_agent - message_name: ListSessionEntityTypesRequest field_entity_map: parent: session diff --git a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/entity_type.proto b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/entity_type.proto similarity index 62% rename from dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/entity_type.proto rename to dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/entity_type.proto index 9d737003..ce36c716 100644 --- a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/entity_type.proto +++ b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/entity_type.proto @@ -1,4 +1,4 @@ -// Copyright 2017 Google Inc. +// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,90 +14,142 @@ syntax = "proto3"; -package google.cloud.dialogflow.v2beta1; +package google.cloud.dialogflow.v2; import "google/api/annotations.proto"; import "google/longrunning/operations.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/field_mask.proto"; +import "google/protobuf/struct.proto"; option cc_enable_arenas = true; -option csharp_namespace = "Google.Cloud.Dialogflow.V2beta1"; -option go_package = "google.golang.org/genproto/googleapis/cloud/dialogflow/v2beta1;dialogflow"; +option csharp_namespace = "Google.Cloud.Dialogflow.V2"; +option go_package = "google.golang.org/genproto/googleapis/cloud/dialogflow/v2;dialogflow"; option java_multiple_files = true; option java_outer_classname = "EntityTypeProto"; -option java_package = "com.google.cloud.dialogflow.v2beta1"; +option java_package = "com.google.cloud.dialogflow.v2"; option objc_class_prefix = "DF"; -// Manages agent entity types. +// Entities are extracted from user input and represent parameters that are +// meaningful to your application. For example, a date range, a proper name +// such as a geographic location or landmark, and so on. Entities represent +// actionable data for your application. // +// When you define an entity, you can also include synonyms that all map to +// that entity. For example, "soft drink", "soda", "pop", and so on. // -// Refer to [documentation](https://api.ai/docs/entities) for more details about -// # entity types. +// There are three types of entities: // -// Standard methods. +// * **System** - entities that are defined by the Dialogflow API for common +// data types such as date, time, currency, and so on. A system entity is +// represented by the `EntityType` type. +// +// * **Developer** - entities that are defined by you that represent +// actionable data that is meaningful to your application. For example, +// you could define a `pizza.sauce` entity for red or white pizza sauce, +// a `pizza.cheese` entity for the different types of cheese on a pizza, +// a `pizza.topping` entity for different toppings, and so on. A developer +// entity is represented by the `EntityType` type. +// +// * **User** - entities that are built for an individual user such as +// favorites, preferences, playlists, and so on. A user entity is +// represented by the [SessionEntityType][google.cloud.dialogflow.v2.SessionEntityType] type. +// +// For more information about entity types, see the +// [Dialogflow documentation](https://dialogflow.com/docs/entities). service EntityTypes { // Returns the list of all entity types in the specified agent. rpc ListEntityTypes(ListEntityTypesRequest) returns (ListEntityTypesResponse) { - option (google.api.http) = { get: "/v2beta1/{parent=projects/*/agent}/entityTypes" }; + option (google.api.http) = { + get: "/v2/{parent=projects/*/agent}/entityTypes" + }; } // Retrieves the specified entity type. rpc GetEntityType(GetEntityTypeRequest) returns (EntityType) { - option (google.api.http) = { get: "/v2beta1/{name=projects/*/agent/entityTypes/*}" }; + option (google.api.http) = { + get: "/v2/{name=projects/*/agent/entityTypes/*}" + }; } // Creates an entity type in the specified agent. rpc CreateEntityType(CreateEntityTypeRequest) returns (EntityType) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*/agent}/entityTypes" body: "entity_type" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*/agent}/entityTypes" + body: "entity_type" + }; } // Updates the specified entity type. rpc UpdateEntityType(UpdateEntityTypeRequest) returns (EntityType) { - option (google.api.http) = { patch: "/v2beta1/{entity_type.name=projects/*/agent/entityTypes/*}" body: "entity_type" }; + option (google.api.http) = { + patch: "/v2/{entity_type.name=projects/*/agent/entityTypes/*}" + body: "entity_type" + }; } // Deletes the specified entity type. rpc DeleteEntityType(DeleteEntityTypeRequest) returns (google.protobuf.Empty) { - option (google.api.http) = { delete: "/v2beta1/{name=projects/*/agent/entityTypes/*}" }; + option (google.api.http) = { + delete: "/v2/{name=projects/*/agent/entityTypes/*}" + }; } // Updates/Creates multiple entity types in the specified agent. // - // Operation + // Operation rpc BatchUpdateEntityTypes(BatchUpdateEntityTypesRequest) returns (google.longrunning.Operation) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*/agent}/entityTypes:batchUpdate" body: "*" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*/agent}/entityTypes:batchUpdate" + body: "*" + }; } // Deletes entity types in the specified agent. // - // Operation + // Operation rpc BatchDeleteEntityTypes(BatchDeleteEntityTypesRequest) returns (google.longrunning.Operation) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*/agent}/entityTypes:batchDelete" body: "*" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*/agent}/entityTypes:batchDelete" + body: "*" + }; } // Creates multiple new entities in the specified entity type (extends the // existing collection of entries). // - // Operation + // Operation rpc BatchCreateEntities(BatchCreateEntitiesRequest) returns (google.longrunning.Operation) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*/agent/entityTypes/*}/entities:batchCreate" body: "*" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*/agent/entityTypes/*}/entities:batchCreate" + body: "*" + }; } // Updates entities in the specified entity type (replaces the existing // collection of entries). // - // Operation + // Operation rpc BatchUpdateEntities(BatchUpdateEntitiesRequest) returns (google.longrunning.Operation) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*/agent/entityTypes/*}/entities:batchUpdate" body: "*" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*/agent/entityTypes/*}/entities:batchUpdate" + body: "*" + }; } // Deletes entities in the specified entity type. // - // Operation + // Operation rpc BatchDeleteEntities(BatchDeleteEntitiesRequest) returns (google.longrunning.Operation) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*/agent/entityTypes/*}/entities:batchDelete" body: "*" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*/agent/entityTypes/*}/entities:batchDelete" + body: "*" + }; } } @@ -147,7 +199,9 @@ message EntityType { AUTO_EXPANSION_MODE_DEFAULT = 1; } - // Required. The unique identifier of the entity type. Format: + // Required for all methods except `create` (`create` populates the name + // automatically. + // The unique identifier of the entity type. Format: // `projects//agent/entityTypes/`. string name = 1; @@ -165,7 +219,7 @@ message EntityType { repeated Entity entities = 6; } -// The request message for [EntityTypes.ListEntityTypes]. +// The request message for [EntityTypes.ListEntityTypes][google.cloud.dialogflow.v2.EntityTypes.ListEntityTypes]. message ListEntityTypesRequest { // Required. The agent to list all entity types from. // Format: `projects//agent`. @@ -173,8 +227,8 @@ message ListEntityTypesRequest { // Optional. The language to list entity synonyms for. If not specified, // the agent's default language is used. - // [More than a dozen languages](https://api.ai/docs/reference/language) - // are supported. + // [More than a dozen + // languages](https://dialogflow.com/docs/reference/language) are supported. // Note: languages must be enabled in the agent, before they can be used. string language_code = 2; @@ -186,7 +240,7 @@ message ListEntityTypesRequest { string page_token = 4; } -// The response message for [EntityTypes.ListEntityTypes]. +// The response message for [EntityTypes.ListEntityTypes][google.cloud.dialogflow.v2.EntityTypes.ListEntityTypes]. message ListEntityTypesResponse { // The list of agent entity types. There will be a maximum number of items // returned based on the page_size field in the request. @@ -197,7 +251,7 @@ message ListEntityTypesResponse { string next_page_token = 2; } -// The request message for [EntityTypes.GetEntityType]. +// The request message for [EntityTypes.GetEntityType][google.cloud.dialogflow.v2.EntityTypes.GetEntityType]. message GetEntityTypeRequest { // Required. The name of the entity type. // Format: `projects//agent/entityTypes/`. @@ -205,13 +259,13 @@ message GetEntityTypeRequest { // Optional. The language to retrieve entity synonyms for. If not specified, // the agent's default language is used. - // [More than a dozen languages](https://api.ai/docs/reference/language) - // are supported. + // [More than a dozen + // languages](https://dialogflow.com/docs/reference/language) are supported. // Note: languages must be enabled in the agent, before they can be used. string language_code = 2; } -// The request message for [EntityTypes.CreateEntityType]. +// The request message for [EntityTypes.CreateEntityType][google.cloud.dialogflow.v2.EntityTypes.CreateEntityType]. message CreateEntityTypeRequest { // Required. The agent to create a entity type for. // Format: `projects//agent`. @@ -222,13 +276,13 @@ message CreateEntityTypeRequest { // Optional. The language of entity synonyms defined in `entity_type`. If not // specified, the agent's default language is used. - // [More than a dozen languages](https://api.ai/docs/reference/language) - // are supported. + // [More than a dozen + // languages](https://dialogflow.com/docs/reference/language) are supported. // Note: languages must be enabled in the agent, before they can be used. string language_code = 3; } -// The request message for [EntityTypes.UpdateEntityType]. +// The request message for [EntityTypes.UpdateEntityType][google.cloud.dialogflow.v2.EntityTypes.UpdateEntityType]. message UpdateEntityTypeRequest { // Required. The entity type to update. // Format: `projects//agent/entityTypes/`. @@ -236,8 +290,8 @@ message UpdateEntityTypeRequest { // Optional. The language of entity synonyms defined in `entity_type`. If not // specified, the agent's default language is used. - // [More than a dozen languages](https://api.ai/docs/reference/language) - // are supported. + // [More than a dozen + // languages](https://dialogflow.com/docs/reference/language) are supported. // Note: languages must be enabled in the agent, before they can be used. string language_code = 2; @@ -245,17 +299,17 @@ message UpdateEntityTypeRequest { google.protobuf.FieldMask update_mask = 3; } -// The request message for [EntityTypes.DeleteEntityType]. +// The request message for [EntityTypes.DeleteEntityType][google.cloud.dialogflow.v2.EntityTypes.DeleteEntityType]. message DeleteEntityTypeRequest { // Required. The name of the entity type to delete. // Format: `projects//agent/entityTypes/`. string name = 1; } -// The request message for [EntityTypes.BatchUpdateEntityTypes]. +// The request message for [EntityTypes.BatchUpdateEntityTypes][google.cloud.dialogflow.v2.EntityTypes.BatchUpdateEntityTypes]. message BatchUpdateEntityTypesRequest { // Required. The name of the agent to update or create entity types in. - // Format: `projects//agents/`. + // Format: `projects//agent`. string parent = 1; // Required. The source of the entity type batch. @@ -264,9 +318,10 @@ message BatchUpdateEntityTypesRequest { // * If `name` is specified, we update an existing entity type. // * If `name` is not specified, we create a new entity type. oneof entity_type_batch { - // The URI to a file containing entity types to update or create. The file - // format can be either a serialized proto (of EntityBatch type) or a JSON - // object. Note: The URI must start with "gs://". + // The URI to a Google Cloud Storage file containing entity types to update + // or create. The file format can either be a serialized proto (of + // EntityBatch type) or a JSON object. Note: The URI must start with + // "gs://". string entity_type_batch_uri = 2; // The collection of entity type to update or create. @@ -275,8 +330,8 @@ message BatchUpdateEntityTypesRequest { // Optional. The language of entity synonyms defined in `entity_types`. If not // specified, the agent's default language is used. - // [More than a dozen languages](https://api.ai/docs/reference/language) - // are supported. + // [More than a dozen + // languages](https://dialogflow.com/docs/reference/language) are supported. // Note: languages must be enabled in the agent, before they can be used. string language_code = 4; @@ -284,13 +339,13 @@ message BatchUpdateEntityTypesRequest { google.protobuf.FieldMask update_mask = 5; } -// The response message for [EntityTypes.BatchUpdateEntityTypes]. +// The response message for [EntityTypes.BatchUpdateEntityTypes][google.cloud.dialogflow.v2.EntityTypes.BatchUpdateEntityTypes]. message BatchUpdateEntityTypesResponse { // The collection of updated or created entity types. repeated EntityType entity_types = 1; } -// The request message for [EntityTypes.BatchDeleteEntityTypes]. +// The request message for [EntityTypes.BatchDeleteEntityTypes][google.cloud.dialogflow.v2.EntityTypes.BatchDeleteEntityTypes]. message BatchDeleteEntityTypesRequest { // Required. The name of the agent to delete all entities types for. Format: // `projects//agent`. @@ -301,7 +356,7 @@ message BatchDeleteEntityTypesRequest { repeated string entity_type_names = 2; } -// The request message for [EntityTypes.BatchCreateEntities]. +// The request message for [EntityTypes.BatchCreateEntities][google.cloud.dialogflow.v2.EntityTypes.BatchCreateEntities]. message BatchCreateEntitiesRequest { // Required. The name of the entity type to create entities in. Format: // `projects//agent/entityTypes/`. @@ -312,13 +367,13 @@ message BatchCreateEntitiesRequest { // Optional. The language of entity synonyms defined in `entities`. If not // specified, the agent's default language is used. - // [More than a dozen languages](https://api.ai/docs/reference/language) - // are supported. + // [More than a dozen + // languages](https://dialogflow.com/docs/reference/language) are supported. // Note: languages must be enabled in the agent, before they can be used. string language_code = 3; } -// The response message for [EntityTypes.BatchCreateEntities]. +// The response message for [EntityTypes.BatchCreateEntities][google.cloud.dialogflow.v2.EntityTypes.BatchCreateEntities]. message BatchUpdateEntitiesRequest { // Required. The name of the entity type to update the entities in. Format: // `projects//agent/entityTypes/`. @@ -329,8 +384,8 @@ message BatchUpdateEntitiesRequest { // Optional. The language of entity synonyms defined in `entities`. If not // specified, the agent's default language is used. - // [More than a dozen languages](https://api.ai/docs/reference/language) - // are supported. + // [More than a dozen + // languages](https://dialogflow.com/docs/reference/language) are supported. // Note: languages must be enabled in the agent, before they can be used. string language_code = 3; @@ -338,7 +393,7 @@ message BatchUpdateEntitiesRequest { google.protobuf.FieldMask update_mask = 4; } -// The request message for [EntityTypes.BatchDeleteEntities]. +// The request message for [EntityTypes.BatchDeleteEntities][google.cloud.dialogflow.v2.EntityTypes.BatchDeleteEntities]. message BatchDeleteEntitiesRequest { // Required. The name of the entity type to delete entries for. Format: // `projects//agent/entityTypes/`. @@ -351,8 +406,8 @@ message BatchDeleteEntitiesRequest { // Optional. The language of entity synonyms defined in `entities`. If not // specified, the agent's default language is used. - // [More than a dozen languages](https://api.ai/docs/reference/language) - // are supported. + // [More than a dozen + // languages](https://dialogflow.com/docs/reference/language) are supported. // Note: languages must be enabled in the agent, before they can be used. string language_code = 3; } diff --git a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/intent.proto b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/intent.proto similarity index 71% rename from dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/intent.proto rename to dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/intent.proto index 0a455193..7d22280f 100644 --- a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/intent.proto +++ b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/intent.proto @@ -1,4 +1,4 @@ -// Copyright 2017 Google Inc. +// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,69 +14,111 @@ syntax = "proto3"; -package google.cloud.dialogflow.v2beta1; +package google.cloud.dialogflow.v2; import "google/api/annotations.proto"; -import "google/cloud/dialogflow/v2beta1/context.proto"; +import "google/cloud/dialogflow/v2/context.proto"; import "google/longrunning/operations.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/field_mask.proto"; import "google/protobuf/struct.proto"; option cc_enable_arenas = true; -option csharp_namespace = "Google.Cloud.Dialogflow.V2beta1"; -option go_package = "google.golang.org/genproto/googleapis/cloud/dialogflow/v2beta1;dialogflow"; +option csharp_namespace = "Google.Cloud.Dialogflow.V2"; +option go_package = "google.golang.org/genproto/googleapis/cloud/dialogflow/v2;dialogflow"; option java_multiple_files = true; option java_outer_classname = "IntentProto"; -option java_package = "com.google.cloud.dialogflow.v2beta1"; +option java_package = "com.google.cloud.dialogflow.v2"; option objc_class_prefix = "DF"; -// Manages agent intents. +// An intent represents a mapping between input from a user and an action to +// be taken by your application. When you pass user input to the +// [DetectIntent][google.cloud.dialogflow.v2.Sessions.DetectIntent] (or +// [StreamingDetectIntent][google.cloud.dialogflow.v2.Sessions.StreamingDetectIntent]) method, the +// Dialogflow API analyzes the input and searches +// for a matching intent. If no match is found, the Dialogflow API returns a +// fallback intent (`is_fallback` = true). // +// You can provide additional information for the Dialogflow API to use to +// match user input to an intent by adding the following to your intent. // -// Refer to [documentation](https://api.ai/docs/intents) for more details about -// # agent intents. +// * **Contexts** - provide additional context for intent analysis. For +// example, if an intent is related to an object in your application that +// plays music, you can provide a context to determine when to match the +// intent if the user input is “turn it off”. You can include a context +// that matches the intent when there is previous user input of +// "play music", and not when there is previous user input of +// "turn on the light". // -// Standard methods. +// * **Events** - allow for matching an intent by using an event name +// instead of user input. Your application can provide an event name and +// related parameters to the Dialogflow API to match an intent. For +// example, when your application starts, you can send a welcome event +// with a user name parameter to the Dialogflow API to match an intent with +// a personalized welcome message for the user. +// +// * **Training phrases** - provide examples of user input to train the +// Dialogflow API agent to better match intents. +// +// For more information about intents, see the +// [Dialogflow documentation](https://dialogflow.com/docs/intents). service Intents { // Returns the list of all intents in the specified agent. rpc ListIntents(ListIntentsRequest) returns (ListIntentsResponse) { - option (google.api.http) = { get: "/v2beta1/{parent=projects/*/agent}/intents" }; + option (google.api.http) = { + get: "/v2/{parent=projects/*/agent}/intents" + }; } // Retrieves the specified intent. rpc GetIntent(GetIntentRequest) returns (Intent) { - option (google.api.http) = { get: "/v2beta1/{name=projects/*/agent/intents/*}" }; + option (google.api.http) = { + get: "/v2/{name=projects/*/agent/intents/*}" + }; } // Creates an intent in the specified agent. rpc CreateIntent(CreateIntentRequest) returns (Intent) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*/agent}/intents" body: "intent" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*/agent}/intents" + body: "intent" + }; } // Updates the specified intent. rpc UpdateIntent(UpdateIntentRequest) returns (Intent) { - option (google.api.http) = { patch: "/v2beta1/{intent.name=projects/*/agent/intents/*}" body: "intent" }; + option (google.api.http) = { + patch: "/v2/{intent.name=projects/*/agent/intents/*}" + body: "intent" + }; } // Deletes the specified intent. rpc DeleteIntent(DeleteIntentRequest) returns (google.protobuf.Empty) { - option (google.api.http) = { delete: "/v2beta1/{name=projects/*/agent/intents/*}" }; + option (google.api.http) = { + delete: "/v2/{name=projects/*/agent/intents/*}" + }; } // Updates/Creates multiple intents in the specified agent. // - // Operation + // Operation rpc BatchUpdateIntents(BatchUpdateIntentsRequest) returns (google.longrunning.Operation) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*/agents/*}/intents:batchUpdate" body: "*" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*/agent}/intents:batchUpdate" + body: "*" + }; } // Deletes intents in the specified agent. // - // Operation + // Operation rpc BatchDeleteIntents(BatchDeleteIntentsRequest) returns (google.longrunning.Operation) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*/agent}/intents:batchDelete" body: "*" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*/agent}/intents:batchDelete" + body: "*" + }; } } @@ -178,7 +220,7 @@ message Intent { bool is_list = 8; } - // Corresponds to the `Response` field in API.AI console. + // Corresponds to the `Response` field in the Dialogflow console. message Message { // The text response message. message Text { @@ -190,6 +232,10 @@ message Intent { message Image { // Optional. The public URI to an image file. string image_uri = 1; + + // Optional. A text description of the image to be used for accessibility, + // e.g., screen readers. + string accessibility_text = 2; } // The quick replies response message. @@ -242,6 +288,9 @@ message Intent { } // The collection of simple response candidates. + // This message in `QueryResult.fulfillment_messages` and + // `WebhookResponse.fulfillment_messages` should contain only one + // `SimpleResponse`. message SimpleResponses { // Required. The list of simple responses. repeated SimpleResponse simple_responses = 1; @@ -388,6 +437,66 @@ message Intent { VIBER = 7; // Actions on Google. + // When using Actions on Google, you can choose one of the specific + // Intent.Message types that mention support for Actions on Google, + // or you can use the advanced Intent.Message.payload field. + // The payload field provides access to AoG features not available in the + // specific message types. + // If using the Intent.Message.payload field, it should have a structure + // similar to the JSON message shown here. For more information, see + // [Actions on Google Webhook + // Format](https://developers.google.com/actions/dialogflow/webhook) + //
{
+      //   "expectUserResponse": true,
+      //   "isSsml": false,
+      //   "noInputPrompts": [],
+      //   "richResponse": {
+      //     "items": [
+      //       {
+      //         "simpleResponse": {
+      //           "displayText": "hi",
+      //           "textToSpeech": "hello"
+      //         }
+      //       }
+      //     ],
+      //     "suggestions": [
+      //       {
+      //         "title": "Say this"
+      //       },
+      //       {
+      //         "title": "or this"
+      //       }
+      //     ]
+      //   },
+      //   "systemIntent": {
+      //     "data": {
+      //       "@type": "type.googleapis.com/google.actions.v2.OptionValueSpec",
+      //       "listSelect": {
+      //         "items": [
+      //           {
+      //             "optionInfo": {
+      //               "key": "key1",
+      //               "synonyms": [
+      //                 "key one"
+      //               ]
+      //             },
+      //             "title": "must not be empty, but unique"
+      //           },
+      //           {
+      //             "optionInfo": {
+      //               "key": "key2",
+      //               "synonyms": [
+      //                 "key two"
+      //               ]
+      //             },
+      //             "title": "must not be empty, but unique"
+      //           }
+      //         ]
+      //       }
+      //     },
+      //     "intent": "actions.intent.OPTION"
+      //   }
+      // }
ACTIONS_ON_GOOGLE = 8; } @@ -405,7 +514,9 @@ message Intent { // The card response. Card card = 4; - // The response containing a custom payload. + // Returns a response containing a custom, platform-specific payload. + // See the Intent.Message.Platform type for a description of the + // structure that may be required for your platform. google.protobuf.Struct payload = 5; // The voice and text-only responses for Actions on Google. @@ -455,7 +566,9 @@ message Intent { WEBHOOK_STATE_ENABLED_FOR_SLOT_FILLING = 2; } - // Required. The unique identifier of this intent. + // Required for all methods except `create` (`create` populates the name + // automatically. + // The unique identifier of this intent. // Format: `projects//agent/intents/`. string name = 1; @@ -472,11 +585,15 @@ message Intent { // Optional. Indicates whether this is a fallback intent. bool is_fallback = 4; - // Optional. Indicates whether Machine Learning is enabled for the intent. - bool ml_enabled = 5; + // Optional. Indicates whether Machine Learning is disabled for the intent. + // Note: If `ml_diabled` setting is set to true, then this intent is not + // taken into account during inference in `ML ONLY` match mode. Also, + // auto-markup in the UI is turned off. + bool ml_disabled = 19; // Optional. The list of context names required for this intent to be // triggered. + // Format: `projects//agent/sessions/-/contexts/`. repeated string input_context_names = 7; // Optional. The collection of event names that trigger the intent. @@ -493,9 +610,9 @@ message Intent { // Optional. The collection of contexts that are activated when the intent // is matched. Context messages in this collection should not set the - // parameters field. - // Format: `projects//agents//sessions/-/contexts/`. + // parameters field. Setting the `lifespan_count` to 0 will reset the context + // when the intent is matched. + // Format: `projects//agent/sessions/-/contexts/`. repeated Context output_contexts = 11; // Optional. Indicates whether to delete all contexts in the current @@ -506,7 +623,7 @@ message Intent { repeated Parameter parameters = 13; // Optional. The collection of rich messages corresponding to the - // `Response` field in API.AI console. + // `Response` field in the Dialogflow console. repeated Message messages = 14; // Optional. The list of platforms for which the first response will be @@ -529,7 +646,7 @@ message Intent { repeated FollowupIntentInfo followup_intent_info = 18; } -// The request message for [Intents.ListIntents]. +// The request message for [Intents.ListIntents][google.cloud.dialogflow.v2.Intents.ListIntents]. message ListIntentsRequest { // Required. The agent to list all intents from. // Format: `projects//agent`. @@ -537,8 +654,8 @@ message ListIntentsRequest { // Optional. The language to list training phrases, parameters and rich // messages for. If not specified, the agent's default language is used. - // [More than a dozen languages](https://api.ai/docs/reference/language) - // are supported. + // [More than a dozen + // languages](https://dialogflow.com/docs/reference/language) are supported. // Note: languages must be enabled in the agent before they can be used. string language_code = 2; @@ -553,7 +670,7 @@ message ListIntentsRequest { string page_token = 5; } -// The response message for [Intents.ListIntents]. +// The response message for [Intents.ListIntents][google.cloud.dialogflow.v2.Intents.ListIntents]. message ListIntentsResponse { // The list of agent intents. There will be a maximum number of items // returned based on the page_size field in the request. @@ -564,7 +681,7 @@ message ListIntentsResponse { string next_page_token = 2; } -// The request message for [Intents.GetIntent]. +// The request message for [Intents.GetIntent][google.cloud.dialogflow.v2.Intents.GetIntent]. message GetIntentRequest { // Required. The name of the intent. // Format: `projects//agent/intents/`. @@ -572,8 +689,8 @@ message GetIntentRequest { // Optional. The language to retrieve training phrases, parameters and rich // messages for. If not specified, the agent's default language is used. - // [More than a dozen languages](https://api.ai/docs/reference/language) - // are supported. + // [More than a dozen + // languages](https://dialogflow.com/docs/reference/language) are supported. // Note: languages must be enabled in the agent, before they can be used. string language_code = 2; @@ -581,7 +698,7 @@ message GetIntentRequest { IntentView intent_view = 3; } -// The request message for [Intents.CreateIntent]. +// The request message for [Intents.CreateIntent][google.cloud.dialogflow.v2.Intents.CreateIntent]. message CreateIntentRequest { // Required. The agent to create a intent for. // Format: `projects//agent`. @@ -592,8 +709,8 @@ message CreateIntentRequest { // Optional. The language of training phrases, parameters and rich messages // defined in `intent`. If not specified, the agent's default language is - // used. [More than a dozen languages](https://api.ai/docs/reference/language) - // are supported. + // used. [More than a dozen + // languages](https://dialogflow.com/docs/reference/language) are supported. // Note: languages must be enabled in the agent, before they can be used. string language_code = 3; @@ -601,7 +718,7 @@ message CreateIntentRequest { IntentView intent_view = 4; } -// The request message for [Intents.UpdateIntent]. +// The request message for [Intents.UpdateIntent][google.cloud.dialogflow.v2.Intents.UpdateIntent]. message UpdateIntentRequest { // Required. The intent to update. // Format: `projects//agent/intents/`. @@ -609,8 +726,8 @@ message UpdateIntentRequest { // Optional. The language of training phrases, parameters and rich messages // defined in `intent`. If not specified, the agent's default language is - // used. [More than a dozen languages](https://api.ai/docs/reference/language) - // are supported. + // used. [More than a dozen + // languages](https://dialogflow.com/docs/reference/language) are supported. // Note: languages must be enabled in the agent, before they can be used. string language_code = 2; @@ -621,24 +738,24 @@ message UpdateIntentRequest { IntentView intent_view = 4; } -// The request message for [Intents.DeleteIntent]. +// The request message for [Intents.DeleteIntent][google.cloud.dialogflow.v2.Intents.DeleteIntent]. message DeleteIntentRequest { // Required. The name of the intent to delete. // Format: `projects//agent/intents/`. string name = 1; } -// The request message for [Intents.BatchUpdateIntents]. +// The request message for [Intents.BatchUpdateIntents][google.cloud.dialogflow.v2.Intents.BatchUpdateIntents]. message BatchUpdateIntentsRequest { // Required. The name of the agent to update or create intents in. - // Format: `projects//agents/`. + // Format: `projects//agent`. string parent = 1; // Required. The source of the intent batch. oneof intent_batch { - // The URI to a file containing intents to update or create. The file - // format can be either a serialized proto (of IntentBatch type) or JSON - // object. Note: The URI must start with "gs://". + // The URI to a Google Cloud Storage file containing intents to update or + // create. The file format can either be a serialized proto (of IntentBatch + // type) or JSON object. Note: The URI must start with "gs://". string intent_batch_uri = 2; // The collection of intents to update or create. @@ -647,8 +764,8 @@ message BatchUpdateIntentsRequest { // Optional. The language of training phrases, parameters and rich messages // defined in `intents`. If not specified, the agent's default language is - // used. [More than a dozen languages](https://api.ai/docs/reference/language) - // are supported. + // used. [More than a dozen + // languages](https://dialogflow.com/docs/reference/language) are supported. // Note: languages must be enabled in the agent, before they can be used. string language_code = 4; @@ -659,20 +776,20 @@ message BatchUpdateIntentsRequest { IntentView intent_view = 6; } -// The response message for [Intents.BatchUpdateIntents]. +// The response message for [Intents.BatchUpdateIntents][google.cloud.dialogflow.v2.Intents.BatchUpdateIntents]. message BatchUpdateIntentsResponse { // The collection of updated or created intents. repeated Intent intents = 1; } -// The request message for [Intents.BatchDeleteIntents]. +// The request message for [Intents.BatchDeleteIntents][google.cloud.dialogflow.v2.Intents.BatchDeleteIntents]. message BatchDeleteIntentsRequest { // Required. The name of the agent to delete all entities types for. Format: // `projects//agent`. string parent = 1; - // Required. The collection of entities to delete. Only the canonical `value` - // must be filled in. + // Required. The collection of intents to delete. Only intent `name` must be + // filled in. repeated Intent intents = 2; } diff --git a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/session.proto b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/session.proto similarity index 80% rename from dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/session.proto rename to dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/session.proto index c3da1875..085e3504 100644 --- a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/session.proto +++ b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/session.proto @@ -1,4 +1,4 @@ -// Copyright 2017 Google Inc. +// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,36 +14,39 @@ syntax = "proto3"; -package google.cloud.dialogflow.v2beta1; +package google.cloud.dialogflow.v2; import "google/api/annotations.proto"; -import "google/cloud/dialogflow/v2beta1/context.proto"; -import "google/cloud/dialogflow/v2beta1/intent.proto"; -import "google/cloud/dialogflow/v2beta1/session_entity_type.proto"; +import "google/cloud/dialogflow/v2/context.proto"; +import "google/cloud/dialogflow/v2/intent.proto"; +import "google/cloud/dialogflow/v2/session_entity_type.proto"; import "google/protobuf/struct.proto"; import "google/rpc/status.proto"; import "google/type/latlng.proto"; option cc_enable_arenas = true; -option csharp_namespace = "Google.Cloud.Dialogflow.V2beta1"; -option go_package = "google.golang.org/genproto/googleapis/cloud/dialogflow/v2beta1;dialogflow"; +option csharp_namespace = "Google.Cloud.Dialogflow.V2"; +option go_package = "google.golang.org/genproto/googleapis/cloud/dialogflow/v2;dialogflow"; option java_multiple_files = true; option java_outer_classname = "SessionProto"; -option java_package = "com.google.cloud.dialogflow.v2beta1"; +option java_package = "com.google.cloud.dialogflow.v2"; option objc_class_prefix = "DF"; -// Manages user sessions. -// -// -// Custom methods. +// A session represents an interaction with a user. You retrieve user input +// and pass it to the [DetectIntent][google.cloud.dialogflow.v2.Sessions.DetectIntent] (or +// [StreamingDetectIntent][google.cloud.dialogflow.v2.Sessions.StreamingDetectIntent]) method to determine +// user intent and respond. service Sessions { // Processes a natural language query and returns structured, actionable data // as a result. This method is not idempotent, because it may cause contexts // and session entity types to be updated, which in turn might affect // results of future queries. rpc DetectIntent(DetectIntentRequest) returns (DetectIntentResponse) { - option (google.api.http) = { post: "/v2beta1/{session=projects/*/agent/sessions/*}:detectIntent" body: "*" }; + option (google.api.http) = { + post: "/v2/{session=projects/*/agent/sessions/*}:detectIntent" + body: "*" + }; } // Processes a natural language query in audio format in a streaming fashion @@ -55,10 +58,10 @@ service Sessions { // The request to detect user's intent. message DetectIntentRequest { // Required. The name of the session this query is sent to. Format: - // `projects//agent/sessions/`. - // It's up to the API caller to choose an appropriate session ID. It can be - // a random number or some type of user identifier (preferably hashed). - // The length of the session ID must not exceed 36 bytes. + // `projects//agent/sessions/`. It's up to the API + // caller to choose an appropriate session ID. It can be a random number or + // some type of user identifier (preferably hashed). The length of the session + // ID must not exceed 36 bytes. string session = 1; // Optional. The parameters of this query. @@ -125,12 +128,12 @@ message QueryParameters { // Represents the query input. It can contain either: // -// 1. an audio config which -// instructs the speech recognizer how to process the speech audio, +// 1. An audio config which +// instructs the speech recognizer how to process the speech audio. // -// 2. a conversational query in the form of text, or +// 2. A conversational query in the form of text,. // -// 3. an event that specifies which intent to trigger. +// 3. An event that specifies which intent to trigger. message QueryInput { // Required. The input specification. oneof input { @@ -156,11 +159,20 @@ message QueryResult { // - If an event was provided as input, `query_text` is not set. string query_text = 1; - // The confidence estimate between 0.0 and 1.0. A higher number + // The language that was triggered during intent detection. + // See [Language Support](https://dialogflow.com/docs/reference/language) + // for a list of the currently supported language codes. + string language_code = 15; + + // The Speech recognition confidence between 0.0 and 1.0. A higher number // indicates an estimated greater likelihood that the recognized words are // correct. The default of 0.0 is a sentinel value indicating that confidence - // was not set. This field is populated if natural speech audio was provided - // as input. + // was not set. + // + // You should not rely on this field as it isn't guaranteed to be accurate, or + // even set. In particular this field isn't set in Webhook calls and for + // StreamingDetectIntent since the streaming endpoint has separate confidence + // estimates per portion of the audio in StreamingRecognitionResult. float speech_recognition_confidence = 2; // The action name from the matched intent. @@ -224,10 +236,10 @@ message QueryResult { message StreamingDetectIntentRequest { // Required. The name of the session the query is sent to. // Format of the session name: - // `projects//agent/sessions/`. - // It’s up to the API caller to choose an appropriate . It can be - // a random number or some type of user identifier (preferably hashed). - // The length of the session ID must not exceed 36 characters. + // `projects//agent/sessions/`. It’s up to the API + // caller to choose an appropriate . It can be a random number or + // some type of user identifier (preferably hashed). The length of the session + // ID must not exceed 36 characters. string session = 1; // Optional. The parameters of this query. @@ -243,10 +255,14 @@ message StreamingDetectIntentRequest { // 3. an event that specifies which intent to trigger. QueryInput query_input = 3; - // Optional. If `true`, the recognizer will detect a single spoken utterance - // in input audio. When it detects that the user has paused or stopped - // speaking, it will cease recognition. This setting is ignored when - // `query_input` is a piece of text or an event. + // Optional. If `false` (default), recognition does not cease until the + // client closes the stream. + // If `true`, the recognizer will detect a single spoken utterance in input + // audio. Recognition ceases when it detects the audio's voice has + // stopped or paused. In this case, once a detected intent is received, the + // client should close the stream and start a new request with a new stream as + // needed. + // This setting is ignored when `query_input` is a piece of text or an event. bool single_utterance = 4; // Optional. The input audio content to be recognized. Must be sent if @@ -341,11 +357,21 @@ message StreamingRecognitionResult { // Populated if and only if `event_type` = `RECOGNITION_EVENT_TRANSCRIPT`. string transcript = 2; + // The default of 0.0 is a sentinel value indicating `confidence` was not set. // If `false`, the `StreamingRecognitionResult` represents an // interim result that may change. If `true`, the recognizer will not return // any further hypotheses about this piece of the audio. May only be populated // for `event_type` = `RECOGNITION_EVENT_TRANSCRIPT`. bool is_final = 3; + + // The Speech confidence between 0.0 and 1.0 for the current portion of audio. + // A higher number indicates an estimated greater likelihood that the + // recognized words are correct. The default of 0.0 is a sentinel value + // indicating that confidence was not set. + // + // This field is typically only provided if `is_final` is true and you should + // not rely on it being accurate or even set. + float confidence = 4; } // Instructs the speech recognizer how to process the audio content. @@ -359,10 +385,10 @@ message InputAudioConfig { int32 sample_rate_hertz = 2; // Required. The language of the supplied audio. Dialogflow does not do - // translations. See [Language Support](https://docs.api.ai/docs/languages) - // for a list of the currently supported language codes. - // Note that queries in the same session do not necessarily need to specify - // the same language. + // translations. See [Language + // Support](https://dialogflow.com/docs/languages) for a list of the + // currently supported language codes. Note that queries in the same session + // do not necessarily need to specify the same language. string language_code = 3; // Optional. The collection of phrase hints which are used to boost accuracy @@ -378,9 +404,10 @@ message TextInput { // Text length must not exceed 256 bytes. string text = 1; - // Required. The language of this conversational query. - // Note that queries in the same session do not necessarily need to specify - // the same language. + // Required. The language of this conversational query. See [Language + // Support](https://dialogflow.com/docs/languages) for a list of the + // currently supported language codes. Note that queries in the same session + // do not necessarily need to specify the same language. string language_code = 2; } @@ -395,6 +422,12 @@ message EventInput { // Optional. The collection of parameters associated with the event. google.protobuf.Struct parameters = 2; + + // Required. The language of this query. See [Language + // Support](https://dialogflow.com/docs/languages) for a list of the + // currently supported language codes. Note that queries in the same session + // do not necessarily need to specify the same language. + string language_code = 3; } // Audio encoding of the audio content sent in the conversational query request. diff --git a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/session_entity_type.proto b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/session_entity_type.proto similarity index 72% rename from dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/session_entity_type.proto rename to dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/session_entity_type.proto index e0f36031..89408df6 100644 --- a/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2beta1/session_entity_type.proto +++ b/dialogflow/Dialogflow/app/src/main/proto/google/cloud/dialogflow/v2/session_entity_type.proto @@ -1,4 +1,4 @@ -// Copyright 2017 Google Inc. +// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,53 +14,70 @@ syntax = "proto3"; -package google.cloud.dialogflow.v2beta1; +package google.cloud.dialogflow.v2; import "google/api/annotations.proto"; -import "google/cloud/dialogflow/v2beta1/entity_type.proto"; +import "google/cloud/dialogflow/v2/entity_type.proto"; import "google/protobuf/empty.proto"; import "google/protobuf/field_mask.proto"; option cc_enable_arenas = true; -option csharp_namespace = "Google.Cloud.Dialogflow.V2beta1"; -option go_package = "google.golang.org/genproto/googleapis/cloud/dialogflow/v2beta1;dialogflow"; +option csharp_namespace = "Google.Cloud.Dialogflow.V2"; +option go_package = "google.golang.org/genproto/googleapis/cloud/dialogflow/v2;dialogflow"; option java_multiple_files = true; option java_outer_classname = "SessionEntityTypeProto"; -option java_package = "com.google.cloud.dialogflow.v2beta1"; +option java_package = "com.google.cloud.dialogflow.v2"; option objc_class_prefix = "DF"; -// Manages session entity types. +// Entities are extracted from user input and represent parameters that are +// meaningful to your application. For example, a date range, a proper name +// such as a geographic location or landmark, and so on. Entities represent +// actionable data for your application. // -// Session entity types can be redefined on a session level, allowing for -// specific concepts, like a user's playlists. +// Session entity types are referred to as **User** entity types and are +// entities that are built for an individual user such as +// favorites, preferences, playlists, and so on. You can redefine a session +// entity type at the session level. // -// -// Standard methods. +// For more information about entity types, see the +// [Dialogflow documentation](https://dialogflow.com/docs/entities). service SessionEntityTypes { // Returns the list of all session entity types in the specified session. rpc ListSessionEntityTypes(ListSessionEntityTypesRequest) returns (ListSessionEntityTypesResponse) { - option (google.api.http) = { get: "/v2beta1/{parent=projects/*/agent/sessions/*}/entityTypes" }; + option (google.api.http) = { + get: "/v2/{parent=projects/*/agent/sessions/*}/entityTypes" + }; } // Retrieves the specified session entity type. rpc GetSessionEntityType(GetSessionEntityTypeRequest) returns (SessionEntityType) { - option (google.api.http) = { get: "/v2beta1/{name=projects/*/agent/sessions/*/entityTypes/*}" }; + option (google.api.http) = { + get: "/v2/{name=projects/*/agent/sessions/*/entityTypes/*}" + }; } // Creates a session entity type. rpc CreateSessionEntityType(CreateSessionEntityTypeRequest) returns (SessionEntityType) { - option (google.api.http) = { post: "/v2beta1/{parent=projects/*/agent/sessions/*}/entityTypes" body: "session_entity_type" }; + option (google.api.http) = { + post: "/v2/{parent=projects/*/agent/sessions/*}/entityTypes" + body: "session_entity_type" + }; } // Updates the specified session entity type. rpc UpdateSessionEntityType(UpdateSessionEntityTypeRequest) returns (SessionEntityType) { - option (google.api.http) = { patch: "/v2beta1/{session_entity_type.name=projects/*/agent/sessions/*/entityTypes/*}" body: "session_entity_type" }; + option (google.api.http) = { + patch: "/v2/{session_entity_type.name=projects/*/agent/sessions/*/entityTypes/*}" + body: "session_entity_type" + }; } // Deletes the specified session entity type. rpc DeleteSessionEntityType(DeleteSessionEntityTypeRequest) returns (google.protobuf.Empty) { - option (google.api.http) = { delete: "/v2beta1/{name=projects/*/agent/sessions/*/entityTypes/*}" }; + option (google.api.http) = { + delete: "/v2/{name=projects/*/agent/sessions/*/entityTypes/*}" + }; } } @@ -85,8 +102,8 @@ message SessionEntityType { // the corresponding developer entity type. // Calls to `ListSessionEntityTypes`, `GetSessionEntityType`, // `CreateSessionEntityType` and `UpdateSessionEntityType` return the full - // collection of entities from the developer entity type and the session - // entity type. + // collection of entities from the developer entity type in the agent's + // default language and the session entity type. ENTITY_OVERRIDE_MODE_SUPPLEMENT = 2; } @@ -104,7 +121,7 @@ message SessionEntityType { repeated EntityType.Entity entities = 3; } -// The request message for [SessionEntityTypes.ListSessionEntityTypes]. +// The request message for [SessionEntityTypes.ListSessionEntityTypes][google.cloud.dialogflow.v2.SessionEntityTypes.ListSessionEntityTypes]. message ListSessionEntityTypesRequest { // Required. The session to list all session entity types from. // Format: `projects//agent/sessions/`. @@ -118,7 +135,7 @@ message ListSessionEntityTypesRequest { string page_token = 3; } -// The response message for [SessionEntityTypes.ListSessionEntityTypes]. +// The response message for [SessionEntityTypes.ListSessionEntityTypes][google.cloud.dialogflow.v2.SessionEntityTypes.ListSessionEntityTypes]. message ListSessionEntityTypesResponse { // The list of session entity types. There will be a maximum number of items // returned based on the page_size field in the request. @@ -129,7 +146,7 @@ message ListSessionEntityTypesResponse { string next_page_token = 2; } -// The request message for [SessionEntityTypes.GetSessionEntityType]. +// The request message for [SessionEntityTypes.GetSessionEntityType][google.cloud.dialogflow.v2.SessionEntityTypes.GetSessionEntityType]. message GetSessionEntityTypeRequest { // Required. The name of the session entity type. Format: // `projects//agent/sessions//entityTypes//agent/sessions/`. @@ -147,7 +164,7 @@ message CreateSessionEntityTypeRequest { SessionEntityType session_entity_type = 2; } -// The request message for [SessionEntityTypes.UpdateSessionEntityType]. +// The request message for [SessionEntityTypes.UpdateSessionEntityType][google.cloud.dialogflow.v2.SessionEntityTypes.UpdateSessionEntityType]. message UpdateSessionEntityTypeRequest { // Required. The entity type to update. Format: // `projects//agent/sessions//entityTypes//agent/sessions//entityTypes//agent/sessions/`. + string session = 4; + // The unique identifier of the response. Contains the same value as // `[Streaming]DetectIntentResponse.response_id`. string response_id = 1; @@ -60,6 +65,28 @@ message WebhookResponse { string source = 3; // Optional. This value is passed directly to `QueryResult.webhook_payload`. + // See the related `fulfillment_messages[i].payload field`, which may be used + // as an alternative to this field. + // + // This field can be used for Actions on Google responses. + // It should have a structure similar to the JSON message shown here. For more + // information, see + // [Actions on Google Webhook + // Format](https://developers.google.com/actions/dialogflow/webhook) + //
{
+  //   "google": {
+  //     "expectUserResponse": true,
+  //     "richResponse": {
+  //       "items": [
+  //         {
+  //           "simpleResponse": {
+  //             "textToSpeech": "this is a simple response"
+  //           }
+  //         }
+  //       ]
+  //     }
+  //   }
+  // }
google.protobuf.Struct payload = 4; // Optional. The collection of output contexts. This value is passed directly @@ -72,10 +99,10 @@ message WebhookResponse { } // Represents the contents of the original request that was passed to -// `[Streaming]DetectIntent call`. +// the `[Streaming]DetectIntent` call. message OriginalDetectIntentRequest { - // The source of this request, e.g., Google, Facebook, Slack. It is set by - // Dialogflow-owned servers. + // The source of this request, e.g., `google`, `facebook`, `slack`. It is set + // by Dialogflow-owned servers. string source = 1; // Optional. This field is set to the value of `QueryParameters.payload` field diff --git a/dialogflow/Dialogflow/build.gradle b/dialogflow/Dialogflow/build.gradle index 3f521ef5..e531f372 100644 --- a/dialogflow/Dialogflow/build.gradle +++ b/dialogflow/Dialogflow/build.gradle @@ -22,7 +22,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0' + classpath 'com.android.tools.build:gradle:3.1.4' classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.2' } } diff --git a/dialogflow/Dialogflow/gradle/wrapper/gradle-wrapper.properties b/dialogflow/Dialogflow/gradle/wrapper/gradle-wrapper.properties index 9d1b2924..c70ba129 100644 --- a/dialogflow/Dialogflow/gradle/wrapper/gradle-wrapper.properties +++ b/dialogflow/Dialogflow/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jul 28 16:45:19 JST 2017 +#Thu Aug 23 10:15:48 JST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip From 1647d0018d58dfe11728df762ccaf2813737c900 Mon Sep 17 00:00:00 2001 From: Yuichi Araki Date: Tue, 11 Jun 2019 11:07:48 +0900 Subject: [PATCH 6/6] Update dependencies Fixes #64 --- speech/Speech/app/build.gradle | 57 +++++++------------ speech/Speech/build.gradle | 4 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- 3 files changed, 24 insertions(+), 41 deletions(-) diff --git a/speech/Speech/app/build.gradle b/speech/Speech/app/build.gradle index 8399f24e..b03e8470 100644 --- a/speech/Speech/app/build.gradle +++ b/speech/Speech/app/build.gradle @@ -18,17 +18,18 @@ apply plugin: 'com.android.application' apply plugin: 'com.google.protobuf' ext { - supportLibraryVersion = '25.4.0' - grpcVersion = '1.4.0' + supportLibraryVersion = '28.0.0' + grpcVersion = '1.21.0' + protobufVersion = '3.8.0' } android { - compileSdkVersion 25 - buildToolsVersion '25.0.3' + compileSdkVersion 28 defaultConfig { applicationId "com.google.cloud.android.speech" - targetSdkVersion 25 + targetSdkVersion 28 + minSdkVersion 16 versionCode 1 versionName '1.0' } @@ -42,17 +43,6 @@ android { } } - productFlavors { - dev { - // Minimum version with platform multi-dex support - minSdkVersion 21 - } - prod { - // Minimum version that can run gRPC (TLS extension) - minSdkVersion 16 - } - } - buildTypes { debug { minifyEnabled false @@ -64,16 +54,11 @@ android { signingConfig signingConfigs.release } } - - configurations.all { - resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.2' - resolutionStrategy.force "com.android.support:support-annotations:$supportLibraryVersion" - } } protobuf { protoc { - artifact = 'com.google.protobuf:protoc:3.3.0' + artifact = "com.google.protobuf:protoc:$protobufVersion" } plugins { javalite { @@ -97,33 +82,31 @@ protobuf { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - // Support libraries - compile "com.android.support:design:$supportLibraryVersion" - compile "com.android.support:cardview-v7:$supportLibraryVersion" - compile "com.android.support:recyclerview-v7:$supportLibraryVersion" + implementation "com.android.support:design:$supportLibraryVersion" + implementation "com.android.support:cardview-v7:$supportLibraryVersion" + implementation "com.android.support:recyclerview-v7:$supportLibraryVersion" // gRPC - compile "io.grpc:grpc-okhttp:$grpcVersion" - compile "io.grpc:grpc-protobuf-lite:$grpcVersion" - compile "io.grpc:grpc-stub:$grpcVersion" - compile 'javax.annotation:javax.annotation-api:1.2' - protobuf 'com.google.protobuf:protobuf-java:3.3.1' + implementation "io.grpc:grpc-okhttp:$grpcVersion" + implementation "io.grpc:grpc-protobuf-lite:$grpcVersion" + implementation "io.grpc:grpc-stub:$grpcVersion" + implementation 'javax.annotation:javax.annotation-api:1.3.2' + protobuf "com.google.protobuf:protobuf-java:$protobufVersion" // OAuth2 for Google API - compile('com.google.auth:google-auth-library-oauth2-http:0.7.0') { + implementation('com.google.auth:google-auth-library-oauth2-http:0.16.1') { exclude module: 'httpclient' } // Tests - testCompile 'junit:junit:4.12' - androidTestCompile 'com.android.support.test:runner:0.5' - androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' } task copySecretKey(type: Copy) { - def File secretKey = file "$System.env.GOOGLE_APPLICATION_CREDENTIALS" + File secretKey = file "$System.env.GOOGLE_APPLICATION_CREDENTIALS" from secretKey.getParent() include secretKey.getName() into 'src/main/res/raw' diff --git a/speech/Speech/build.gradle b/speech/Speech/build.gradle index 75f079d1..8e67f15b 100644 --- a/speech/Speech/build.gradle +++ b/speech/Speech/build.gradle @@ -22,8 +22,8 @@ buildscript { maven { url 'https://maven.google.com' } } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' - classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0' + classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.8' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/speech/Speech/gradle/wrapper/gradle-wrapper.properties b/speech/Speech/gradle/wrapper/gradle-wrapper.properties index 618ded6a..26028d52 100644 --- a/speech/Speech/gradle/wrapper/gradle-wrapper.properties +++ b/speech/Speech/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Apr 03 14:01:53 JST 2017 +#Tue Jun 11 10:38:36 JST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip