diff --git a/Speech2Speech/.gitignore b/Speech2Speech/.gitignore new file mode 100644 index 00000000..2b75303a --- /dev/null +++ b/Speech2Speech/.gitignore @@ -0,0 +1,13 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/Speech2Speech/app/.gitignore b/Speech2Speech/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/Speech2Speech/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/Speech2Speech/app/build.gradle b/Speech2Speech/app/build.gradle new file mode 100644 index 00000000..e30b643e --- /dev/null +++ b/Speech2Speech/app/build.gradle @@ -0,0 +1,59 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.example.translate" + minSdkVersion 26 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + packagingOptions { + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/LICENSE' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/license.txt' + exclude 'META-INF/NOTICE' + exclude 'META-INF/NOTICE.txt' + exclude 'META-INF/notice.txt' + exclude 'META-INF/ASL2.0' + exclude 'META-INF/INDEX.LIST' + exclude 'META-INF/io.netty.versions.properties' + } + +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'com.android.support:design:28.0.0' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + implementation 'com.google.firebase:firebase-auth:18.1.0' + implementation 'com.firebaseui:firebase-ui-auth:4.1.0' + implementation 'com.google.firebase:firebase-messaging:19.0.1' + implementation 'com.google.firebase:firebase-functions:18.1.0' + + implementation 'com.google.cloud:google-cloud-translate:1.87.0' + implementation group: 'com.google.cloud', name: 'google-cloud-texttospeech', version: '0.45.0-beta' + implementation group: 'com.google.cloud', name: 'google-cloud-speech', version: '1.17.0' + + implementation 'com.android.volley:volley:1.1.1' + implementation 'com.google.cloud:google-cloud-dialogflow:0.98.0-alpha' + implementation group: 'io.grpc', name: 'grpc-okhttp', version: '1.21.0' + implementation group: 'io.grpc', name: 'grpc-netty', version: '1.21.0' +} + +apply plugin: 'com.google.gms.google-services' diff --git a/Speech2Speech/app/gradle/wrapper/gradle-wrapper.jar b/Speech2Speech/app/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..f6b961fd Binary files /dev/null and b/Speech2Speech/app/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Speech2Speech/app/gradle/wrapper/gradle-wrapper.properties b/Speech2Speech/app/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..b8d30e27 --- /dev/null +++ b/Speech2Speech/app/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Aug 25 22:35:10 PDT 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/Speech2Speech/app/gradlew b/Speech2Speech/app/gradlew new file mode 100644 index 00000000..cccdd3d5 --- /dev/null +++ b/Speech2Speech/app/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# 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 + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# 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 +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +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" -a "$nonstop" = "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 + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/Speech2Speech/app/gradlew.bat b/Speech2Speech/app/gradlew.bat new file mode 100644 index 00000000..e95643d6 --- /dev/null +++ b/Speech2Speech/app/gradlew.bat @@ -0,0 +1,84 @@ +@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 + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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= + +@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 Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_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=%* + +: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/Speech2Speech/app/proguard-rules.pro b/Speech2Speech/app/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/Speech2Speech/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/Speech2Speech/app/src/androidTest/java/com/example/translate/ExampleInstrumentedTest.java b/Speech2Speech/app/src/androidTest/java/com/example/translate/ExampleInstrumentedTest.java new file mode 100644 index 00000000..95f840e9 --- /dev/null +++ b/Speech2Speech/app/src/androidTest/java/com/example/translate/ExampleInstrumentedTest.java @@ -0,0 +1,27 @@ +package com.example.translate; + +import android.content.Context; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.example.translate", appContext.getPackageName()); + } +} diff --git a/Speech2Speech/app/src/main/AndroidManifest.xml b/Speech2Speech/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..7475fc2a --- /dev/null +++ b/Speech2Speech/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Speech2Speech/app/src/main/java/com/example/translate/AppController.java b/Speech2Speech/app/src/main/java/com/example/translate/AppController.java new file mode 100644 index 00000000..7e6d9229 --- /dev/null +++ b/Speech2Speech/app/src/main/java/com/example/translate/AppController.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019 Google LLC + * + * 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.example.translate; + +import android.app.Application; +import android.content.Context; +import android.media.MediaPlayer; +import android.os.Environment; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +public class AppController extends Application { + + public static final String TOKEN_RECEIVED = "TOKEN_RECEIVED"; + public static final String SESSION_ID = "sessionId"; + public static String PROJECT_ID = ""; + public static Context context; + + public static void playAudio(byte[] byteArray) { + MediaPlayer mediaPlayer = new MediaPlayer(); + try { + File tempFile = File.createTempFile("dialogFlow", null, Environment.getExternalStorageDirectory()); + tempFile.deleteOnExit(); + FileOutputStream fos = new FileOutputStream(tempFile); + fos.write(byteArray); + fos.close(); + mediaPlayer.reset(); + FileInputStream fis = new FileInputStream(tempFile); + mediaPlayer.setDataSource(fis.getFD()); + + mediaPlayer.prepare(); + mediaPlayer.start(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + @Override + public void onCreate() { + super.onCreate(); + context = getApplicationContext(); + PROJECT_ID = getApplicationContext().getString(R.string.gcp_project_id); + } + +} diff --git a/Speech2Speech/app/src/main/java/com/example/translate/adapter/ChatRecyclerViewAdapter.java b/Speech2Speech/app/src/main/java/com/example/translate/adapter/ChatRecyclerViewAdapter.java new file mode 100644 index 00000000..1d140304 --- /dev/null +++ b/Speech2Speech/app/src/main/java/com/example/translate/adapter/ChatRecyclerViewAdapter.java @@ -0,0 +1,83 @@ +/* + * Copyright 2019 Google LLC + * + * 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.example.translate.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.example.translate.R; +import com.example.translate.model.ChatMsgModel; + +import java.util.ArrayList; + +public class ChatRecyclerViewAdapter extends RecyclerView.Adapter { + + private ArrayList chatMsgModels; + + public ChatRecyclerViewAdapter(ArrayList chatMsgModels) { + this.chatMsgModels = chatMsgModels; + + } + + @Override + public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View itemView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.custom_chat_recyclerview_item, parent, false); + + return new MyViewHolder(itemView); + } + + @Override + public void onBindViewHolder(MyViewHolder holder, final int position) { + final ChatMsgModel chatMsgModel = chatMsgModels.get(position); + + if (chatMsgModel.getType() == 1) { // Message Sent + holder.tvMsgSent.setText(chatMsgModel.getMsg()); + holder.tvMsgSent.setVisibility(View.VISIBLE); + holder.tvMsgReceived.setVisibility(View.GONE); + } else { + holder.tvMsgReceived.setText(chatMsgModel.getMsg()); + holder.tvMsgReceived.setVisibility(View.VISIBLE); + holder.tvMsgSent.setVisibility(View.GONE); + } + + + } + + @Override + public int getItemCount() { + return chatMsgModels.size(); + } + + public class MyViewHolder extends RecyclerView.ViewHolder { + RelativeLayout rlMain; + TextView tvMsgSent; + TextView tvMsgReceived; + + public MyViewHolder(View view) { + super(view); + rlMain = view.findViewById(R.id.rlMain); + tvMsgSent = view.findViewById(R.id.tvMsgSent); + tvMsgReceived = view.findViewById(R.id.tvMsgReceived); + } + } +} \ No newline at end of file diff --git a/Speech2Speech/app/src/main/java/com/example/translate/adapter/LanguageAdapter.java b/Speech2Speech/app/src/main/java/com/example/translate/adapter/LanguageAdapter.java new file mode 100644 index 00000000..619a1945 --- /dev/null +++ b/Speech2Speech/app/src/main/java/com/example/translate/adapter/LanguageAdapter.java @@ -0,0 +1,72 @@ +package com.example.translate.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.cloud.translate.Language; + +import java.util.ArrayList; + +public class LanguageAdapter extends ArrayAdapter { + + private Context context; + private int resource; + private ArrayList objects; + + public LanguageAdapter(@NonNull Context context, int resource, @NonNull ArrayList objects) { + super(context, resource, objects); + this.context = context; + this.resource = resource; + this.objects = objects; + } + + @Override + public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + + Holder holder; + + if (convertView == null) { + holder = new Holder(); + convertView = LayoutInflater.from(context).inflate(resource, null); + holder.text1 = convertView.findViewById(android.R.id.text1); + convertView.setTag(holder); + } else { + holder = (Holder) convertView.getTag(); + } + + holder.text1.setText(objects.get(position).getName() + " (" + objects.get(position).getCode() + ")"); + + return convertView; + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + + Holder holder; + + if (convertView == null) { + holder = new Holder(); + convertView = LayoutInflater.from(context).inflate(resource, null); + holder.text1 = convertView.findViewById(android.R.id.text1); + convertView.setTag(holder); + } else { + holder = (Holder) convertView.getTag(); + } + + holder.text1.setText(objects.get(position).getName() + " (" + objects.get(position).getCode() + ")"); + + return convertView; + } + + class Holder { + TextView text1; + } +} diff --git a/Speech2Speech/app/src/main/java/com/example/translate/adapter/VoiceAdapter.java b/Speech2Speech/app/src/main/java/com/example/translate/adapter/VoiceAdapter.java new file mode 100644 index 00000000..8478e119 --- /dev/null +++ b/Speech2Speech/app/src/main/java/com/example/translate/adapter/VoiceAdapter.java @@ -0,0 +1,72 @@ +package com.example.translate.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.cloud.texttospeech.v1beta1.Voice; + +import java.util.ArrayList; + +public class VoiceAdapter extends ArrayAdapter { + + private Context context; + private int resource; + private ArrayList objects; + + public VoiceAdapter(@NonNull Context context, int resource, @NonNull ArrayList objects) { + super(context, resource, objects); + this.context = context; + this.resource = resource; + this.objects = objects; + } + + @Override + public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + + Holder holder; + + if (convertView == null) { + holder = new Holder(); + convertView = LayoutInflater.from(context).inflate(resource, null); + holder.text1 = convertView.findViewById(android.R.id.text1); + convertView.setTag(holder); + } else { + holder = (Holder) convertView.getTag(); + } + + holder.text1.setText(objects.get(position).getName() + " (" + objects.get(position).getSsmlGender().name() + ")"); + + return convertView; + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + + Holder holder; + + if (convertView == null) { + holder = new Holder(); + convertView = LayoutInflater.from(context).inflate(resource, null); + holder.text1 = convertView.findViewById(android.R.id.text1); + convertView.setTag(holder); + } else { + holder = (Holder) convertView.getTag(); + } + + holder.text1.setText(objects.get(position).getName() + " (" + objects.get(position).getSsmlGender().name() + ")"); + + return convertView; + } + + class Holder { + TextView text1; + } +} diff --git a/Speech2Speech/app/src/main/java/com/example/translate/model/ChatMsgModel.java b/Speech2Speech/app/src/main/java/com/example/translate/model/ChatMsgModel.java new file mode 100644 index 00000000..61a1f960 --- /dev/null +++ b/Speech2Speech/app/src/main/java/com/example/translate/model/ChatMsgModel.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Google LLC + * + * 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.example.translate.model; + +public class ChatMsgModel { + + private String msg; + private int type; + + public ChatMsgModel(String msg, int type) { + this.msg = msg; + this.type = type; + } + + public String getMsg() { + return msg; + } + + public int getType() { + return type; + } + +} diff --git a/Speech2Speech/app/src/main/java/com/example/translate/service/MyFirebaseCloudMessagingService.java b/Speech2Speech/app/src/main/java/com/example/translate/service/MyFirebaseCloudMessagingService.java new file mode 100644 index 00000000..1d08fe19 --- /dev/null +++ b/Speech2Speech/app/src/main/java/com/example/translate/service/MyFirebaseCloudMessagingService.java @@ -0,0 +1,66 @@ + +/* + * Copyright 2019 Google LLC + * + * 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.example.translate.service; + +import android.content.Intent; +import android.util.Log; + +import com.example.translate.AppController; +import com.example.translate.utils.AuthUtils; +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Locale; + +public class MyFirebaseCloudMessagingService extends FirebaseMessagingService { + + private static final String TAG = MyFirebaseCloudMessagingService.class.getSimpleName(); + + @Override + public void onMessageReceived(RemoteMessage remoteMessage) { + Log.i("FirebaseMessage", "From: " + remoteMessage.getFrom()); + + // Check if message contains a notification payload. + if (remoteMessage.getNotification() != null) { + Log.i(TAG, "Notification Body: " + remoteMessage.getNotification().getBody()); + handleNotification(remoteMessage.getNotification().getTitle(), remoteMessage.getNotification().getBody()); + } + } + + /** + * function to save the token data in the AppController + * + * @param expiryTime : expiry time received from FCM + * @param token : token received from FCM + */ + private void handleNotification(String expiryTime, String token) { + try { + AuthUtils.expiryTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).parse(expiryTime); + } catch (ParseException e) { + e.printStackTrace(); + } + AuthUtils.token = token; + + Intent intent = new Intent(AppController.TOKEN_RECEIVED); + intent.putExtra("type", "token"); + sendBroadcast(intent); + } + +} \ No newline at end of file diff --git a/Speech2Speech/app/src/main/java/com/example/translate/ui/MainActivity.java b/Speech2Speech/app/src/main/java/com/example/translate/ui/MainActivity.java new file mode 100644 index 00000000..a55bf5c0 --- /dev/null +++ b/Speech2Speech/app/src/main/java/com/example/translate/ui/MainActivity.java @@ -0,0 +1,296 @@ +package com.example.translate.ui; + +import android.app.ProgressDialog; +import android.content.BroadcastReceiver; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.MediaRecorder; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.provider.MediaStore; +import android.text.TextUtils; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.translate.AppController; +import com.example.translate.R; +import com.example.translate.adapter.ChatRecyclerViewAdapter; +import com.example.translate.model.ChatMsgModel; +import com.example.translate.utils.ApiRequest; +import com.example.translate.utils.AuthUtils; + +import java.util.ArrayList; +import java.util.Date; + +public class MainActivity extends AppCompatActivity { + + private static ChatRecyclerViewAdapter chatRecyclerViewAdapter; + private static ArrayList chatMsgModels; + private static RecyclerView rvChats; + private ApiRequest apiRequest; + + private EditText etMsg; + private ImageButton btnSend; + private ImageButton btnMic; + private AlertDialog alert; + private String message = ""; + private String fileName = ""; + + private String sourceLanguageCode; + private String targetLanguageCode; + private int ssmlGenderValue; + + /** + * Broadcast receiver to hide the progress dialog when token is received + */ + private BroadcastReceiver br = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (alert != null && alert.isShowing()) { + alert.dismiss(); + } + if (intent.getStringExtra("type").equals("token")) { + if (!TextUtils.isEmpty(fileName)) { + sendMsg("", fileName); + } else if (!TextUtils.isEmpty(message)) { + sendMsg(message, ""); + } else if (!etMsg.getText().toString().trim().equals("")) { + sendMsg(etMsg.getText().toString().trim(), ""); + } + } else if (intent.getStringExtra("type").equals("addMsg")) { + addMsg(intent.getStringExtra("message"), intent.getIntExtra("messageType", 0)); + } + } + }; + + /** + * function to scroll the recyclerview at the bottom after each message sent or received + */ + private static void scrollToBottom() { + new Handler().post(new Runnable() { + @Override + public void run() { + if (chatMsgModels.size() > 0) { + rvChats.smoothScrollToPosition(chatMsgModels.size() - 1); + } + } + }); + } + + /** + * function to addMessage in the recyclerview + * + * @param msg : message to add + * @param type : Type of message (sent|received) + */ + public static void addMsg(String msg, int type) { + chatMsgModels.add(new ChatMsgModel(msg, type)); + chatRecyclerViewAdapter.notifyDataSetChanged(); + scrollToBottom(); + } + + /** + * function to show the progress dialog + */ + private void showProgressDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage("Fetching auth token..."); + builder.setCancelable(false); + + alert = builder.create(); + alert.show(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + sourceLanguageCode = getIntent().getStringExtra("sourceLanguageCode"); + targetLanguageCode = getIntent().getStringExtra("targetLanguageCode"); + ssmlGenderValue = getIntent().getIntExtra("ssmlGenderValue", 0); + + initViews(); + setupRecyclerView(); + initListeners(); + + apiRequest = new ApiRequest(); + + } + + /** + * function to initialize the views + */ + private void initViews() { + etMsg = findViewById(R.id.etMsg); + btnSend = findViewById(R.id.btnSend); + btnMic = findViewById(R.id.btnMic); + rvChats = findViewById(R.id.rvChat); + } + + /** + * function to initialize the recyclerview + */ + private void setupRecyclerView() { + rvChats.setLayoutManager(new LinearLayoutManager(this)); + chatMsgModels = new ArrayList<>(); + + chatRecyclerViewAdapter = new ChatRecyclerViewAdapter(chatMsgModels); + rvChats.setAdapter(chatRecyclerViewAdapter); + } + + /** + * function to initialize the onClick listeners + */ + private void initListeners() { + btnSend.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + sendMsg(etMsg.getText().toString(), ""); + scrollToBottom(); + } + }); + + btnMic.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + recordAudio("temp.mp3"); + } + }); + } + + + private void getNewToken() { + showProgressDialog(); + if (AuthUtils.checkSignIn()) { + AuthUtils.callFirebaseFunction(); + } + } + + /** + * function to send the message + * + * @param msg : message sent from user + */ + private void sendMsg(String msg, String fileName) { + if (!TextUtils.isEmpty(msg) || !TextUtils.isEmpty(fileName)) { + // check if the token is received and expiry time is received and not expired + if (AuthUtils.expiryTime != null && !AuthUtils.token.equals("") && AuthUtils.expiryTime.getTime() > System.currentTimeMillis()) { + if (!TextUtils.isEmpty(msg)) { + addMsg(msg, 1); + } + new APIRequest(AuthUtils.token, AuthUtils.expiryTime, msg, fileName, sourceLanguageCode, targetLanguageCode, ssmlGenderValue).execute(); + etMsg.setText(""); + this.message = ""; + this.fileName = ""; + } else { + // get new token if expired or not received + getNewToken(); + } + } else { + Toast.makeText(this, "Please enter or say some message to send.", Toast.LENGTH_SHORT).show(); + } + } + + @Override + protected void onResume() { + super.onResume(); + IntentFilter intentFilter = new IntentFilter(AppController.TOKEN_RECEIVED); + registerReceiver(br, intentFilter); + } + + @Override + protected void onPause() { + super.onPause(); + if (br != null) { + try { + unregisterReceiver(br); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + private void recordAudio(final String fileName) { + final MediaRecorder recorder = new MediaRecorder(); + ContentValues values = new ContentValues(3); + values.put(MediaStore.MediaColumns.TITLE, fileName); + recorder.setAudioSource(MediaRecorder.AudioSource.MIC); + recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); + recorder.setAudioSamplingRate(16000); + recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); + recorder.setOutputFile(Environment.getExternalStorageDirectory() + "/" + fileName); + try { + recorder.prepare(); + } catch (Exception e) { + e.printStackTrace(); + } + + final ProgressDialog mProgressDialog = new ProgressDialog(this); + mProgressDialog.setTitle("Recording"); + mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + mProgressDialog.setButton("Stop recording", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + mProgressDialog.dismiss(); + recorder.stop(); + recorder.release(); + sendMsg("", Environment.getExternalStorageDirectory() + "/" + fileName); + } + }); + + mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface p1) { + recorder.stop(); + recorder.release(); + } + }); + recorder.start(); + mProgressDialog.show(); + } + + private class APIRequest extends AsyncTask { + private String token; + private Date expiryTime; + private String msg; + private String fileName; + private String sourceLanguageCode; + private String targetLanguageCode; + private int ssmlGenderValue; + + public APIRequest(String token, Date expiryTime, String msg, String fileName, String sourceLanguageCode, String targetLanguageCode, int ssmlGenderValue) { + this.token = token; + this.expiryTime = expiryTime; + this.msg = msg; + this.fileName = fileName; + this.sourceLanguageCode = sourceLanguageCode; + this.targetLanguageCode = targetLanguageCode; + this.ssmlGenderValue = ssmlGenderValue; + } + + @Override + protected String doInBackground(Void... voids) { + return apiRequest.callAPI(token, expiryTime, msg, fileName, sourceLanguageCode, targetLanguageCode, ssmlGenderValue); + } + + @Override + protected void onPostExecute(String response) { + super.onPostExecute(response); + if (response != null) { + addMsg(response, 0); + } + } + } + +} diff --git a/Speech2Speech/app/src/main/java/com/example/translate/ui/SettingsActivity.java b/Speech2Speech/app/src/main/java/com/example/translate/ui/SettingsActivity.java new file mode 100644 index 00000000..9f4ce0de --- /dev/null +++ b/Speech2Speech/app/src/main/java/com/example/translate/ui/SettingsActivity.java @@ -0,0 +1,342 @@ +package com.example.translate.ui; + +import android.Manifest; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.AsyncTask; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.Spinner; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; + +import com.example.translate.AppController; +import com.example.translate.R; +import com.example.translate.adapter.LanguageAdapter; +import com.example.translate.adapter.VoiceAdapter; +import com.example.translate.utils.ApiRequest; +import com.example.translate.utils.AuthUtils; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; +import com.google.cloud.texttospeech.v1beta1.Voice; +import com.google.cloud.translate.Language; +import com.google.firebase.auth.AuthResult; +import com.google.firebase.iid.InstanceIdResult; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class SettingsActivity extends AppCompatActivity { + + + private Spinner spTranslateFrom; + private Spinner spTranslateTo; + private Spinner spTtsType; + private Spinner spVoiceType; + private Button btnProceed; + private AlertDialog alert; + private String languageListCode = ""; + private ApiRequest apiRequest; + + private ArrayList sourceLanguageList = new ArrayList<>(); + private ArrayList targetLanguageList = new ArrayList<>(); + private ArrayList voicesList = new ArrayList<>(); + private ArrayList filteredVoicesList = new ArrayList<>(); + private ArrayList ttsTypes = new ArrayList<>(); + private LanguageAdapter sourceLanguageListArrayAdapter; + private LanguageAdapter targetLanguageListArrayAdapter; + private VoiceAdapter voicesListArrayAdapter; + private ArrayAdapter ttsListArrayAdapter; + + /** + * Broadcast receiver to hide the progress dialog when token is received + */ + private BroadcastReceiver br = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + alert.dismiss(); + fetchSupportedLanguages(languageListCode); + } + }; + + private void fetchSupportedLanguages(String sourceLanguageCode) { + if (AuthUtils.expiryTime != null && !AuthUtils.token.equals("") && AuthUtils.expiryTime.getTime() > System.currentTimeMillis()) { + new LanguageList(AuthUtils.token, AuthUtils.expiryTime, sourceLanguageCode).execute(); + } else { + // get new token if expired or not received + languageListCode = sourceLanguageCode; + getNewToken(); + } + } + + private void fetchVoiceslist(String targetLanguageCode) { + if (AuthUtils.expiryTime != null && !AuthUtils.token.equals("") && AuthUtils.expiryTime.getTime() > System.currentTimeMillis()) { + new VoicesList(AuthUtils.token, AuthUtils.expiryTime, targetLanguageCode).execute(); + } else { + // get new token if expired or not received + languageListCode = targetLanguageCode; + getNewToken(); + } + } + + private void getNewToken() { + showProgressDialog(); + if (AuthUtils.checkSignIn()) { + AuthUtils.callFirebaseFunction(); + } + } + + /** + * function to show the progress dialog + */ + private void showProgressDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage("Fetching auth token..."); + builder.setCancelable(false); + + alert = builder.create(); + alert.show(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_settings); + + if (AppController.PROJECT_ID.equals("GCP_PROJECT_ID")) { + Toast.makeText(this, "Please update the GCP_PROJECT_ID in strings.xml", Toast.LENGTH_LONG).show(); + finish(); + return; + } + + checkPermissions(); + + apiRequest = new ApiRequest(); + + initViews(); + initListeners(); + + + AuthUtils.signInAnonymously(this, new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + // Sign in success, update UI with the signed-in user's information + AuthUtils.getFirebaseInstanceId(new OnSuccessListener() { + @Override + public void onSuccess(InstanceIdResult instanceIdResult) { + String deviceToken = instanceIdResult.getToken(); + AuthUtils.firebaseInstanceId = deviceToken; + Log.i("fcmId", deviceToken); + fetchSupportedLanguages(""); + } + }); + + Toast.makeText(SettingsActivity.this, "Sign In was successful", + Toast.LENGTH_SHORT).show(); + } else { + // If sign in fails, display a message to the user. + Toast.makeText(SettingsActivity.this, "Authentication failed.", + Toast.LENGTH_SHORT).show(); + } + } + }); + + loadTtsTypes(); + + } + + private void loadTtsTypes() { + ttsTypes.add("WaveNet"); + ttsTypes.add("Standard"); + ttsListArrayAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, ttsTypes); + spTtsType.setAdapter(ttsListArrayAdapter); + + filterVoiceTypes(); + } + + private void loadVoices() { + voicesListArrayAdapter = new VoiceAdapter(SettingsActivity.this, android.R.layout.simple_list_item_1, filteredVoicesList); + spVoiceType.setAdapter(voicesListArrayAdapter); + } + + private void initViews() { + spTranslateFrom = findViewById(R.id.spTranslateFrom); + spTranslateTo = findViewById(R.id.spTranslateTo); + spTtsType = findViewById(R.id.spTtsType); + spVoiceType = findViewById(R.id.spVoiceType); + btnProceed = findViewById(R.id.btnProceed); + } + + private void initListeners() { + spTranslateFrom.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + fetchSupportedLanguages(sourceLanguageList.get(position).getCode()); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + spTranslateTo.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + fetchVoiceslist(targetLanguageList.get(position).getCode()); + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + spTtsType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + + filterVoiceTypes(); + + loadVoices(); + + } + + @Override + public void onNothingSelected(AdapterView parent) { + + } + }); + + btnProceed.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(SettingsActivity.this, MainActivity.class); + intent.putExtra("sourceLanguageCode", sourceLanguageList.get(spTranslateFrom.getSelectedItemPosition()).getCode()); + intent.putExtra("targetLanguageCode", targetLanguageList.get(spTranslateTo.getSelectedItemPosition()).getCode()); + if (filteredVoicesList.size() > 0) { + intent.putExtra("ssmlGenderValue", filteredVoicesList.get(spVoiceType.getSelectedItemPosition()).getSsmlGenderValue()); + } + startActivity(intent); + } + }); + } + + private void filterVoiceTypes() { + filteredVoicesList = new ArrayList<>(); + + for (int i = 0; i < voicesList.size(); i++) { + if (voicesList.get(i).getName().toLowerCase().contains(ttsTypes.get(spTtsType.getSelectedItemPosition()).toLowerCase())) { + filteredVoicesList.add(voicesList.get(i)); + } + } + } + + public void checkPermissions() { + + ArrayList arrPerm = new ArrayList<>(); + arrPerm.add(Manifest.permission.INTERNET); + arrPerm.add(Manifest.permission.RECORD_AUDIO); + arrPerm.add(Manifest.permission.READ_EXTERNAL_STORAGE); + arrPerm.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); + + if (!arrPerm.isEmpty()) { + String[] permissions = new String[arrPerm.size()]; + permissions = arrPerm.toArray(permissions); + ActivityCompat.requestPermissions(this, permissions, 1); + } + } + + @Override + protected void onResume() { + super.onResume(); + IntentFilter intentFilter = new IntentFilter(AppController.TOKEN_RECEIVED); + registerReceiver(br, intentFilter); + } + + @Override + protected void onPause() { + super.onPause(); + if (br != null) { + try { + unregisterReceiver(br); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + private class LanguageList extends AsyncTask> { + + private String sourceLanguageCode; + private String token; + private Date expiryTime; + + public LanguageList(String token, Date expiryTime, String sourceLanguageCode) { + this.token = token; + this.expiryTime = expiryTime; + this.sourceLanguageCode = sourceLanguageCode; + } + + @Override + protected List doInBackground(Void... voids) { + return apiRequest.getSupportedLanguages(token, expiryTime, sourceLanguageCode); + } + + @Override + protected void onPostExecute(List response) { + super.onPostExecute(response); + if (TextUtils.isEmpty(sourceLanguageCode)) { + sourceLanguageList.addAll(response); + sourceLanguageListArrayAdapter = new LanguageAdapter(SettingsActivity.this, android.R.layout.simple_list_item_1, sourceLanguageList); + spTranslateFrom.setAdapter(sourceLanguageListArrayAdapter); + } else { + targetLanguageList.addAll(response); + targetLanguageListArrayAdapter = new LanguageAdapter(SettingsActivity.this, android.R.layout.simple_list_item_1, targetLanguageList); + spTranslateTo.setAdapter(targetLanguageListArrayAdapter); + } + } + } + + private class VoicesList extends AsyncTask> { + + private String sourceLanguageCode; + private String token; + private Date expiryTime; + + public VoicesList(String token, Date expiryTime, String sourceLanguageCode) { + this.token = token; + this.expiryTime = expiryTime; + this.sourceLanguageCode = sourceLanguageCode; + } + + @Override + protected List doInBackground(Void... voids) { + return apiRequest.listVoices(token, expiryTime, sourceLanguageCode); + } + + @Override + protected void onPostExecute(List response) { + super.onPostExecute(response); + if (response != null) { + voicesList.addAll(response); + filteredVoicesList.addAll(response); + loadVoices(); + } + } + } +} diff --git a/Speech2Speech/app/src/main/java/com/example/translate/utils/ApiRequest.java b/Speech2Speech/app/src/main/java/com/example/translate/utils/ApiRequest.java new file mode 100644 index 00000000..3e9e52d1 --- /dev/null +++ b/Speech2Speech/app/src/main/java/com/example/translate/utils/ApiRequest.java @@ -0,0 +1,341 @@ +/* + * Copyright 2019 Google LLC + * + * 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.example.translate.utils; + +import android.content.Intent; +import android.text.TextUtils; +import android.util.Log; + +import com.example.translate.AppController; +import com.google.api.gax.core.FixedCredentialsProvider; +import com.google.api.gax.rpc.ApiStreamObserver; +import com.google.api.gax.rpc.BidiStreamingCallable; +import com.google.auth.Credentials; +import com.google.auth.oauth2.AccessToken; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.speech.v1p1beta1.RecognitionConfig; +import com.google.cloud.speech.v1p1beta1.SpeechClient; +import com.google.cloud.speech.v1p1beta1.SpeechRecognitionAlternative; +import com.google.cloud.speech.v1p1beta1.SpeechSettings; +import com.google.cloud.speech.v1p1beta1.StreamingRecognitionConfig; +import com.google.cloud.speech.v1p1beta1.StreamingRecognitionResult; +import com.google.cloud.speech.v1p1beta1.StreamingRecognizeRequest; +import com.google.cloud.speech.v1p1beta1.StreamingRecognizeResponse; +import com.google.cloud.texttospeech.v1beta1.AudioConfig; +import com.google.cloud.texttospeech.v1beta1.AudioEncoding; +import com.google.cloud.texttospeech.v1beta1.ListVoicesResponse; +import com.google.cloud.texttospeech.v1beta1.SynthesisInput; +import com.google.cloud.texttospeech.v1beta1.SynthesizeSpeechResponse; +import com.google.cloud.texttospeech.v1beta1.TextToSpeechClient; +import com.google.cloud.texttospeech.v1beta1.TextToSpeechSettings; +import com.google.cloud.texttospeech.v1beta1.Voice; +import com.google.cloud.texttospeech.v1beta1.VoiceSelectionParams; +import com.google.cloud.translate.Language; +import com.google.cloud.translate.Translate; +import com.google.cloud.translate.TranslateOptions; +import com.google.cloud.translate.v3beta1.LocationName; +import com.google.cloud.translate.v3beta1.TranslateTextRequest; +import com.google.cloud.translate.v3beta1.TranslateTextResponse; +import com.google.cloud.translate.v3beta1.TranslationServiceClient; +import com.google.cloud.translate.v3beta1.TranslationServiceSettings; +import com.google.common.util.concurrent.SettableFuture; +import com.google.protobuf.ByteString; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +public class ApiRequest { + + private String token = null; + private Date tokenExpiration = null; + + public ApiRequest() { + // Variables needed to retrieve an auth token + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", java.util.Locale.getDefault()); + simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + public static List listVoices(String token, Date tokenExpiration, String language) { + + try { + AccessToken accessToken = new AccessToken(token, tokenExpiration); + Credentials credentials = GoogleCredentials.create(accessToken); + FixedCredentialsProvider fixedCredentialsProvider = FixedCredentialsProvider.create(credentials); + TextToSpeechSettings textToSpeechSettings = TextToSpeechSettings.newBuilder().setCredentialsProvider(fixedCredentialsProvider).build(); + try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create(textToSpeechSettings)) { + + ListVoicesResponse listVoicesResponse = textToSpeechClient.listVoices(language); + List voices = listVoicesResponse.getVoicesList(); + + for (int i = 0; i < voices.size(); i++) { + Log.i("voices", voices.get(i).getName()); + } + + return voices; + + + } + } catch (Exception ex) { + ex.printStackTrace(); + } + + return null; + } + + public List getSupportedLanguages(String token, Date tokenExpiration, String sourceLanguageCode) { + + try { + AccessToken accessToken = new AccessToken(token, tokenExpiration); + Credentials credentials = GoogleCredentials.create(accessToken); + FixedCredentialsProvider fixedCredentialsProvider = FixedCredentialsProvider.create(credentials); + Translate translate = TranslateOptions.newBuilder().setProjectId(AppController.PROJECT_ID).setCredentials(fixedCredentialsProvider.getCredentials()).build().getService(); + List languages; + + if (TextUtils.isEmpty(sourceLanguageCode)) { + languages = translate.listSupportedLanguages(); + } else { + languages = translate.listSupportedLanguages(Translate.LanguageListOption.targetLanguage(sourceLanguageCode)); + } + + for (Language language : languages) { + System.out.printf("Name: %s, Code: %s\n", language.getName(), language.getCode()); + } + + return languages; + + } catch (Exception ex) { + ex.printStackTrace(); + } + + return null; + } + + /** + * function to call: detect Intent Sentiment Analysis | Detect Intent With TTS | KnowledgeBase + * + * @param accessToken : access token received from fcm + * @param expiryTime : expiry time received from fcm + * @return : response from the server + */ + public String callAPI(String accessToken, Date expiryTime, String text, String fileName, String sourceLanguageCode, String targetLanguageCode, int ssmlGenderValue) { + this.token = accessToken; + this.tokenExpiration = expiryTime; + + String response = ""; + if (!TextUtils.isEmpty(fileName)) { + String convertedText = speechToText(accessToken, expiryTime, fileName, sourceLanguageCode); + addMsg(convertedText); + response = translateText(accessToken, expiryTime, convertedText, sourceLanguageCode, targetLanguageCode); + } else if (!TextUtils.isEmpty(text)) { + response = translateText(accessToken, expiryTime, text, sourceLanguageCode, targetLanguageCode); + } + + textToSpeech(response, ssmlGenderValue, targetLanguageCode); + return response; + } + + private void addMsg(String message) { + Intent intent = new Intent(AppController.TOKEN_RECEIVED); + intent.putExtra("type", "addMsg"); + intent.putExtra("message", message); + intent.putExtra("messageType", 1); + AppController.context.sendBroadcast(intent); + } + + /** + * function to getting the results from the dialogflow + * + * @return : response from the server + */ + private String translateText(String token, Date tokenExpiration, String text, String sourceLanguageCode, String targetLanguageCode) { + try { + AccessToken accessToken = new AccessToken(token, tokenExpiration); + Credentials credentials = GoogleCredentials.create(accessToken); + FixedCredentialsProvider fixedCredentialsProvider = FixedCredentialsProvider.create(credentials); + TranslationServiceSettings translationServiceSettings = TranslationServiceSettings.newBuilder().setCredentialsProvider(fixedCredentialsProvider).build(); + try (TranslationServiceClient translationServiceClient = TranslationServiceClient.create(translationServiceSettings)) { + + LocationName locationName = + LocationName.newBuilder().setProject(AppController.PROJECT_ID).setLocation("global").build(); + + TranslateTextRequest translateTextRequest = + TranslateTextRequest.newBuilder() + .setParent(locationName.toString()) + .setMimeType("text/plain") + .setSourceLanguageCode(sourceLanguageCode) + .setTargetLanguageCode(targetLanguageCode) + .addContents(text) + .build(); + + // Call the API + TranslateTextResponse response = translationServiceClient.translateText(translateTextRequest); + System.out.format( + "Translated Text: %s", response.getTranslationsList().get(0).getTranslatedText()); + return response.getTranslationsList().get(0).getTranslatedText(); + + } catch (Exception e) { + throw new RuntimeException("Couldn't create client.", e); + } + } catch (Exception ex) { + ex.printStackTrace(); + return ex.getMessage(); + } + } + + private void textToSpeech(String text, int ssmlGenderValue, String languageCode) { + + try { + AccessToken accessToken = new AccessToken(token, tokenExpiration); + Credentials credentials = GoogleCredentials.create(accessToken); + FixedCredentialsProvider fixedCredentialsProvider = FixedCredentialsProvider.create(credentials); + TextToSpeechSettings textToSpeechSettings = TextToSpeechSettings.newBuilder().setCredentialsProvider(fixedCredentialsProvider).build(); + try (TextToSpeechClient textToSpeechClient = TextToSpeechClient.create(textToSpeechSettings)) { + // Set the text input to be synthesized + SynthesisInput input = SynthesisInput.newBuilder() + .setText(text) + .build(); + + // Build the voice request, select the language code ("en-US") and the ssml voice gender + // ("neutral") + VoiceSelectionParams voice = VoiceSelectionParams.newBuilder() + .setLanguageCode(languageCode) + .setSsmlGenderValue(ssmlGenderValue) + .build(); + + // Select the type of audio file you want returned + AudioConfig audioConfig = AudioConfig.newBuilder() + .setAudioEncoding(AudioEncoding.MP3) + .build(); + + // Perform the text-to-speech request on the text input with the selected voice parameters and + // audio file type + SynthesizeSpeechResponse response = textToSpeechClient.synthesizeSpeech(input, voice, + audioConfig); + + // Get the audio contents from the response + ByteString audioContents = response.getAudioContent(); + AppController.playAudio(audioContents.toByteArray()); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + public String speechToText(String token, Date tokenExpiration, String fileName, String languageCode) { + try { + AccessToken accessToken = new AccessToken(token, tokenExpiration); + Credentials credentials = GoogleCredentials.create(accessToken); + FixedCredentialsProvider fixedCredentialsProvider = FixedCredentialsProvider.create(credentials); + SpeechSettings speechSettings = + SpeechSettings.newBuilder() + .setCredentialsProvider(fixedCredentialsProvider) + .build(); + try (SpeechClient speechClient = SpeechClient.create(speechSettings)) { + + // Reads the audio file into memory + Path path = Paths.get(fileName); + byte[] data = Files.readAllBytes(path); + ByteString audioBytes = ByteString.copyFrom(data); + + // Builds the sync recognize request + RecognitionConfig recConfig = RecognitionConfig.newBuilder() + .setEncoding(RecognitionConfig.AudioEncoding.MP3) + .setSampleRateHertz(16000) + .setLanguageCode(languageCode) + .setMaxAlternatives(30) + .setModel("default") + .build(); + + StreamingRecognitionConfig config = + StreamingRecognitionConfig.newBuilder().setConfig(recConfig).build(); + + class ResponseApiStreamingObserver implements ApiStreamObserver { + private final SettableFuture> future = SettableFuture.create(); + private final List messages = new java.util.ArrayList(); + + @Override + public void onNext(T message) { + messages.add(message); + } + + @Override + public void onError(Throwable t) { + future.setException(t); + } + + @Override + public void onCompleted() { + future.set(messages); + } + + // Returns the SettableFuture object to get received messages / exceptions. + public SettableFuture> future() { + return future; + } + } + + ResponseApiStreamingObserver responseObserver = + new ResponseApiStreamingObserver<>(); + + BidiStreamingCallable callable = + speechClient.streamingRecognizeCallable(); + + ApiStreamObserver requestObserver = + callable.bidiStreamingCall(responseObserver); + + // The first request must **only** contain the audio configuration: + requestObserver.onNext( + StreamingRecognizeRequest.newBuilder().setStreamingConfig(config).build()); + + // Subsequent requests must **only** contain the audio data. + requestObserver.onNext( + StreamingRecognizeRequest.newBuilder() + .setAudioContent(ByteString.copyFrom(data)) + .build()); + + // Mark transmission as completed after sending the data. + requestObserver.onCompleted(); + + List responses = responseObserver.future().get(); + String responseText = ""; + for (StreamingRecognizeResponse response : responses) { + // For streaming recognize, the results list has one is_final result (if available) followed + // by a number of in-progress results (if iterim_results is true) for subsequent utterances. + // Just print the first result here. + StreamingRecognitionResult result = response.getResultsList().get(0); + // There can be several alternative transcripts for a given chunk of speech. Just use the + // first (most likely) one here. + SpeechRecognitionAlternative alternative = result.getAlternativesList().get(0); + responseText += alternative.getTranscript(); + } + + return responseText; + } + } catch (Exception ex) { + ex.printStackTrace(); + } + + return ""; + } +} + + diff --git a/Speech2Speech/app/src/main/java/com/example/translate/utils/AuthUtils.java b/Speech2Speech/app/src/main/java/com/example/translate/utils/AuthUtils.java new file mode 100644 index 00000000..e10cfec5 --- /dev/null +++ b/Speech2Speech/app/src/main/java/com/example/translate/utils/AuthUtils.java @@ -0,0 +1,72 @@ +/* + * Copyright 2019 Google LLC + * + * 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.example.translate.utils; + +import android.app.Activity; + +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.firebase.auth.AuthResult; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.functions.FirebaseFunctions; +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.InstanceIdResult; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class AuthUtils { + + public static String firebaseInstanceId = ""; + public static FirebaseAuth firebaseAuth; + + public static String token = ""; + public static Date expiryTime; + + /** + * function to call the firebase function which will send the fcm message containing token and expiry time to the device + */ + public static void callFirebaseFunction() { + Map data = new HashMap<>(); + data.put("deviceID", firebaseInstanceId); + + FirebaseFunctions.getInstance() + .getHttpsCallable("getOAuthToken") + .call(data); + } + + public static void signInAnonymously(final Activity activity, OnCompleteListener onCompleteListener) { + firebaseAuth = FirebaseAuth.getInstance(); + firebaseAuth.signInAnonymously() + .addOnCompleteListener(activity, onCompleteListener); + } + + /** + * function to check the user is logged in + * + * @return boolean : returns true if user is logged inn + */ + public static boolean checkSignIn() { + return firebaseAuth != null && firebaseAuth.getCurrentUser() != null; + } + + public static void getFirebaseInstanceId(OnSuccessListener onSuccessListener) { + FirebaseInstanceId.getInstance().getInstanceId().addOnSuccessListener(onSuccessListener); + } + +} diff --git a/Speech2Speech/app/src/main/res/drawable-hdpi/ic_mic.png b/Speech2Speech/app/src/main/res/drawable-hdpi/ic_mic.png new file mode 100755 index 00000000..b27f5171 Binary files /dev/null and b/Speech2Speech/app/src/main/res/drawable-hdpi/ic_mic.png differ diff --git a/Speech2Speech/app/src/main/res/drawable-hdpi/ic_send.png b/Speech2Speech/app/src/main/res/drawable-hdpi/ic_send.png new file mode 100755 index 00000000..324421c6 Binary files /dev/null and b/Speech2Speech/app/src/main/res/drawable-hdpi/ic_send.png differ diff --git a/Speech2Speech/app/src/main/res/drawable-mdpi/ic_mic.png b/Speech2Speech/app/src/main/res/drawable-mdpi/ic_mic.png new file mode 100755 index 00000000..68102401 Binary files /dev/null and b/Speech2Speech/app/src/main/res/drawable-mdpi/ic_mic.png differ diff --git a/Speech2Speech/app/src/main/res/drawable-mdpi/ic_send.png b/Speech2Speech/app/src/main/res/drawable-mdpi/ic_send.png new file mode 100755 index 00000000..55475546 Binary files /dev/null and b/Speech2Speech/app/src/main/res/drawable-mdpi/ic_send.png differ diff --git a/Speech2Speech/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/Speech2Speech/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..1f6bb290 --- /dev/null +++ b/Speech2Speech/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/Speech2Speech/app/src/main/res/drawable-xhdpi/ic_mic.png b/Speech2Speech/app/src/main/res/drawable-xhdpi/ic_mic.png new file mode 100755 index 00000000..41f74c5b Binary files /dev/null and b/Speech2Speech/app/src/main/res/drawable-xhdpi/ic_mic.png differ diff --git a/Speech2Speech/app/src/main/res/drawable-xhdpi/ic_send.png b/Speech2Speech/app/src/main/res/drawable-xhdpi/ic_send.png new file mode 100755 index 00000000..14acf57e Binary files /dev/null and b/Speech2Speech/app/src/main/res/drawable-xhdpi/ic_send.png differ diff --git a/Speech2Speech/app/src/main/res/drawable-xxhdpi/ic_mic.png b/Speech2Speech/app/src/main/res/drawable-xxhdpi/ic_mic.png new file mode 100755 index 00000000..d5da5bee Binary files /dev/null and b/Speech2Speech/app/src/main/res/drawable-xxhdpi/ic_mic.png differ diff --git a/Speech2Speech/app/src/main/res/drawable-xxhdpi/ic_send.png b/Speech2Speech/app/src/main/res/drawable-xxhdpi/ic_send.png new file mode 100755 index 00000000..6c50f9e8 Binary files /dev/null and b/Speech2Speech/app/src/main/res/drawable-xxhdpi/ic_send.png differ diff --git a/Speech2Speech/app/src/main/res/drawable-xxxhdpi/ic_mic.png b/Speech2Speech/app/src/main/res/drawable-xxxhdpi/ic_mic.png new file mode 100755 index 00000000..51419b34 Binary files /dev/null and b/Speech2Speech/app/src/main/res/drawable-xxxhdpi/ic_mic.png differ diff --git a/Speech2Speech/app/src/main/res/drawable-xxxhdpi/ic_send.png b/Speech2Speech/app/src/main/res/drawable-xxxhdpi/ic_send.png new file mode 100755 index 00000000..a081b468 Binary files /dev/null and b/Speech2Speech/app/src/main/res/drawable-xxxhdpi/ic_send.png differ diff --git a/Speech2Speech/app/src/main/res/drawable-xxxhdpi/rounded_corner.xml b/Speech2Speech/app/src/main/res/drawable-xxxhdpi/rounded_corner.xml new file mode 100644 index 00000000..982884bd --- /dev/null +++ b/Speech2Speech/app/src/main/res/drawable-xxxhdpi/rounded_corner.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Speech2Speech/app/src/main/res/drawable/ic_launcher_background.xml b/Speech2Speech/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..0d025f9b --- /dev/null +++ b/Speech2Speech/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Speech2Speech/app/src/main/res/layout/activity_main.xml b/Speech2Speech/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..20e71532 --- /dev/null +++ b/Speech2Speech/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Speech2Speech/app/src/main/res/layout/activity_settings.xml b/Speech2Speech/app/src/main/res/layout/activity_settings.xml new file mode 100644 index 00000000..db78ac9a --- /dev/null +++ b/Speech2Speech/app/src/main/res/layout/activity_settings.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + +