diff --git a/.gitignore b/.gitignore
index 3bdf4fe951..4277d95b4f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -371,3 +371,4 @@ ASALocalRun/
*.lzma
*.cab
.DS_Store
+/.project
diff --git a/solutions/android/VirtualAssistantClient/.gitignore b/solutions/android/VirtualAssistantClient/.gitignore
new file mode 100644
index 0000000000..2b75303ac5
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/.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/solutions/android/VirtualAssistantClient/app/.gitignore b/solutions/android/VirtualAssistantClient/app/.gitignore
new file mode 100644
index 0000000000..9551f44fa5
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/.gitignore
@@ -0,0 +1,2 @@
+/build
+!/src/debug
diff --git a/solutions/android/VirtualAssistantClient/app/build.gradle b/solutions/android/VirtualAssistantClient/app/build.gradle
new file mode 100644
index 0000000000..b5f153a547
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/build.gradle
@@ -0,0 +1,66 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "com.microsoft.bot.builder.solutions.virtualassistant"
+ minSdkVersion 24
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+
+ // Android Support
+ implementation 'com.android.support:recyclerview-v7:28.0.0'
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ implementation 'com.android.support:design:28.0.0'
+
+ // Butterknife
+ implementation 'com.jakewharton:butterknife:8.8.1'
+ annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
+
+ // for Events
+ implementation 'org.greenrobot:eventbus:3.1.1'
+
+ // for adaptive card rendering, see https://mvnrepository.com/artifact/io.adaptivecards/adaptivecards-android
+ implementation 'io.adaptivecards:adaptivecards-android:1.2.0-beta1'
+
+ // JSON
+ implementation "com.google.code.gson:gson:2.8.4"
+
+ // rxJava
+ implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.7.0@aar'
+ implementation 'io.reactivex:rxandroid:1.2.1'
+ implementation 'io.reactivex:rxjava:1.1.6'
+
+ // Material Dialogs
+ implementation 'com.afollestad.material-dialogs:core:0.9.6.0'
+
+ // fused API
+ implementation "com.google.android.gms:play-services-location:16.0.0"
+ implementation "com.android.support:support-media-compat:28.0.0"
+//fixes fused API outdated library
+ implementation "com.android.support:support-v4:28.0.0"//fixes fused API outdated library
+
+ // Direct Line Speech (local module)
+ implementation project(':directlinespeech')
+}
diff --git a/solutions/android/VirtualAssistantClient/app/proguard-rules.pro b/solutions/android/VirtualAssistantClient/app/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/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/solutions/android/VirtualAssistantClient/app/src/debug/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/configuration/DefaultConfiguration.java b/solutions/android/VirtualAssistantClient/app/src/debug/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/configuration/DefaultConfiguration.java
new file mode 100644
index 0000000000..14966873bd
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/debug/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/configuration/DefaultConfiguration.java
@@ -0,0 +1,22 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.activities.configuration;
+
+public class DefaultConfiguration {
+
+ // Replace below with your own subscription key
+ public static final String COGNITIVE_SERVICES_SUBSCRIPTION_KEY = "YOUR_KEY_HERE";//TODO
+
+ public static final String BOT_ID = "YOUR_BOT_ID_HERE";//TODO
+
+ public static final String SPEECH_REGION = "westus2";
+
+ public static final String VOICE_NAME = "Microsoft Server Speech Text to Speech Voice (en-US, JessaNeural)";
+
+ public static final String DIRECT_LINE_CONSTANT = "directline";
+ public static final String USER_NAME = "User";
+ public static final String USER_ID = "YOUR_USER_ID_HERE";//TODO
+
+ public static final String LOCALE = "en-us";
+
+ public static final String GEOLOCATION_LAT = "47.617305";
+ public static final String GEOLOCATION_LON = "-122.192217";
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/AndroidManifest.xml b/solutions/android/VirtualAssistantClient/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..dcb2085435
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/AndroidManifest.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/assets/keywords/computer/kws.table b/solutions/android/VirtualAssistantClient/app/src/main/assets/keywords/computer/kws.table
new file mode 100644
index 0000000000..1140e4a54c
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/assets/keywords/computer/kws.table differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/assets/webchat.html b/solutions/android/VirtualAssistantClient/app/src/main/assets/webchat.html
new file mode 100644
index 0000000000..32d9d23767
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/assets/webchat.html
@@ -0,0 +1,353 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/MainApplication.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/MainApplication.java
new file mode 100644
index 0000000000..98464d82e9
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/MainApplication.java
@@ -0,0 +1,28 @@
+package com.microsoft.bot.builder.solutions.virtualassistant;
+
+import android.app.Application;
+import android.content.Intent;
+import android.support.v4.content.ContextCompat;
+
+import com.microsoft.bot.builder.solutions.virtualassistant.service.SpeechService;
+
+public class MainApplication extends Application {
+
+ // STATE
+ private static MainApplication instance;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ instance = this;
+
+ // start service but don't initialize it yet
+ Intent intent = new Intent(this, SpeechService.class);
+ intent.setAction(SpeechService.ACTION_START_FOREGROUND_SERVICE);
+ ContextCompat.startForegroundService(this, intent);
+ }
+
+ public static MainApplication getInstance(){
+ return instance;
+ }
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/BaseActivity.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/BaseActivity.java
new file mode 100644
index 0000000000..785f6df122
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/BaseActivity.java
@@ -0,0 +1,220 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.activities;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.design.widget.Snackbar;
+import android.support.v4.app.ActivityCompat;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Toast;
+
+import com.microsoft.bot.builder.solutions.virtualassistant.R;
+import com.microsoft.bot.builder.solutions.virtualassistant.service.ServiceBinder;
+import com.microsoft.bot.builder.solutions.virtualassistant.service.SpeechService;
+
+/**
+ * This base class provides functionality that is reusable in Activities of this app
+ */
+public abstract class BaseActivity extends AppCompatActivity {
+
+ // Constants
+ private static final Integer PERMISSION_REQUEST_RECORD_AUDIO = 101;
+ private static final Integer PERMISSION_REQUEST_FINE_LOCATION = 102;
+ private static final String SHARED_PREFS_NAME = "my_shared_prefs";
+ protected static final String SHARED_PREF_SHOW_TEXTINPUT = "SHARED_PREF_SHOW_TEXTINPUT";
+
+ // State
+ private SharedPreferences sharedPreferences;
+ protected SpeechService speechServiceBinder;
+
+ // Override these
+ protected void permissionDenied(String manifestPermission){};
+ protected void permissionGranted(String manifestPermission){};
+ protected void serviceConnected(){};
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ sharedPreferences = getSharedPreferences(SHARED_PREFS_NAME, MODE_PRIVATE);
+ setupMainWindowDisplayMode();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ setSystemUiVisilityMode();
+ }
+
+ private void setupMainWindowDisplayMode() {
+ View decorView = setSystemUiVisilityMode();
+ decorView.setOnSystemUiVisibilityChangeListener(visibility -> {
+ setSystemUiVisilityMode(); // Needed to avoid exiting immersive_sticky when keyboard is displayed
+ });
+ }
+
+ private View setSystemUiVisilityMode() {
+ View decorView = getWindow().getDecorView();
+ int options;
+ options =
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
+ | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+
+ decorView.setSystemUiVisibility(options);
+ return decorView;
+ }
+
+ protected void hideKeyboardFrom(View view) {
+ InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+
+ protected void showSnackbar(View view, String message){
+ Snackbar.make(view, message, Snackbar.LENGTH_SHORT)
+ .setAction("Action", null)
+ .show();
+ }
+
+ protected void showToast(String message){
+ Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
+ }
+
+ protected void putBooleanSharedPref(String prefName, boolean value){
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putBoolean(prefName, value);
+ editor.commit();
+ }
+
+ protected boolean getBooleanSharedPref(String prefName) {
+ return sharedPreferences.getBoolean(prefName, false);
+ }
+
+ /**
+ * Requests the RECORD_AUDIO and WRITE_EXTERNAL_STORAGE permissions as they are "Dangerous"
+ * If the permission has been denied previously, a dialog with extra rationale info will prompt
+ * the user to grant the permission, otherwise it is requested directly.
+ */
+ protected void requestRecordAudioPermissions() {
+ if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
+ // Provide an additional rationale to the user if the permission was not granted
+ // and the user would benefit from additional context for the use of the permission.
+ // For example if the user has previously denied the permission.
+ new AlertDialog.Builder(this)
+ .setTitle(getString(R.string.permission_record_audio_title))
+ .setMessage(getString(R.string.permission_record_audio_rationale))
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.yes, (dialog, which) -> {
+ //re-request
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.RECORD_AUDIO},
+ PERMISSION_REQUEST_RECORD_AUDIO);
+ })
+ .show();
+ } else {
+ // RECORD_AUDIO permission has not been granted yet. Request it directly.
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.RECORD_AUDIO},
+ PERMISSION_REQUEST_RECORD_AUDIO);
+ }
+ }
+
+ protected void requestFineLocationPermissions() {
+ if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
+ // Provide an additional rationale to the user if the permission was not granted
+ // and the user would benefit from additional context for the use of the permission.
+ // For example if the user has previously denied the permission.
+ new AlertDialog.Builder(this)
+ .setTitle(getString(R.string.permission_access_fine_location_title))
+ .setMessage(getString(R.string.permission_access_fine_location_rationale))
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.yes, (dialog, which) -> {
+ //re-request
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
+ PERMISSION_REQUEST_FINE_LOCATION);
+ })
+ .show();
+ } else {
+ // ACCESS_FINE_LOCATION permission has not been granted yet. Request it directly.
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
+ PERMISSION_REQUEST_FINE_LOCATION);
+ }
+ }
+
+ /**
+ * Callback received when a permissions request has been completed.
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+
+ // Received permission result for RECORD_AUDIO permission");
+ if (requestCode == PERMISSION_REQUEST_RECORD_AUDIO) {
+ if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // RECORD_AUDIO permission has been granted
+ permissionGranted(Manifest.permission.RECORD_AUDIO);
+ }
+ else {
+ permissionDenied(Manifest.permission.RECORD_AUDIO);
+ }
+ }
+
+ // Received permission result for ACCESS_FINE_LOCATION permission");
+ if (requestCode == PERMISSION_REQUEST_FINE_LOCATION) {
+ if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ // permission has been granted
+ permissionGranted(Manifest.permission.ACCESS_FINE_LOCATION);
+ }
+ else {
+ permissionDenied(Manifest.permission.ACCESS_FINE_LOCATION);
+ }
+ }
+ }
+
+ public void doBindService() {
+ Intent intent = null;
+ intent = new Intent(this, SpeechService.class);
+ bindService(intent, myConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ public ServiceConnection myConnection = new ServiceConnection() {
+
+ public void onServiceConnected(ComponentName className, IBinder binder) {
+ speechServiceBinder = ((ServiceBinder) binder).getSpeechService();
+ Log.d("ServiceConnection","connected");
+ // now use speechServiceBinder to execute methods in the service
+ serviceConnected();
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ Log.d("ServiceConnection","disconnected");
+ speechServiceBinder = null;
+ }
+ };
+
+ protected void initializeAndConnect(){
+ if (speechServiceBinder != null) {
+ speechServiceBinder.initializeSpeechSdk(true);
+ speechServiceBinder.getSpeechSdk().connectAsync();
+ } else {
+ Log.e("ServiceConnection", "do not have a binding to the service");
+ }
+ }
+
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/botconfiguration/BotConfigurationActivity.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/botconfiguration/BotConfigurationActivity.java
new file mode 100644
index 0000000000..789ffefac4
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/botconfiguration/BotConfigurationActivity.java
@@ -0,0 +1,120 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.activities.botconfiguration;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.design.widget.TextInputEditText;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+
+import com.microsoft.bot.builder.solutions.directlinespeech.model.Configuration;
+import com.microsoft.bot.builder.solutions.virtualassistant.R;
+import com.microsoft.bot.builder.solutions.virtualassistant.activities.BaseActivity;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.OnEditorAction;
+
+/**
+ * Bot Configuration Activity - settings to change the connection to the Bot
+ * Note: settings are saved when OK is pressed
+ */
+public class BotConfigurationActivity extends BaseActivity {
+
+ // VIEWS
+ @BindView(R.id.service_key) TextInputEditText serviceKey;
+ @BindView(R.id.service_region) TextInputEditText serviceRegion;
+ @BindView(R.id.bot_id) TextInputEditText botId;
+ @BindView(R.id.voice_name) TextInputEditText voiceName;
+ @BindView(R.id.user_id) TextInputEditText userId;
+ @BindView(R.id.locale) TextInputEditText locale;
+ @BindView(R.id.geolocation_lat) TextInputEditText locationLat;
+ @BindView(R.id.geolocation_lon) TextInputEditText locationLon;
+
+ // CONSTANTS
+ private static final int CONTENT_VIEW = R.layout.activity_bot_configuration;
+
+ // STATE
+ private Configuration configuration;
+
+ public static Intent getNewIntent(Context context) {
+ return new Intent(context, BotConfigurationActivity.class);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(CONTENT_VIEW);
+ ButterKnife.bind(this);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (speechServiceBinder == null) {
+ doBindService();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (speechServiceBinder != null) {
+ unbindService(myConnection);
+ speechServiceBinder = null;
+ }
+ super.onStop();
+ }
+
+ @Override
+ protected void serviceConnected() {
+ showConfiguration();
+ }
+
+ @OnEditorAction({R.id.service_key, R.id.service_region, R.id.bot_id, R.id.voice_name, R.id.user_id, R.id.locale, R.id.geolocation_lat, R.id.geolocation_lon})
+ boolean onEditorAction(int actionId, KeyEvent key){
+ boolean handled = false;
+ if (actionId == EditorInfo.IME_ACTION_SEND || (key != null && key.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
+ hideKeyboardFrom(getCurrentFocus());
+ handled = true;
+ }
+ return handled;
+ }
+
+ @OnClick(R.id.btn_cancel)
+ public void onClickCancel() {
+ finish();
+ }
+
+ @OnClick(R.id.btn_ok)
+ public void onClickOk() {
+ saveConfiguration();// must save updated config first
+ initializeAndConnect();// re-init service to make it read updated config
+ finish();
+ }
+
+ private void showConfiguration(){
+ configuration = speechServiceBinder.getConfiguration();
+ serviceKey.setText(configuration.serviceKey);
+ serviceRegion.setText(configuration.serviceRegion);
+ botId.setText(configuration.botId);
+ voiceName.setText(configuration.voiceName);
+ userId.setText(configuration.userId);
+ locale.setText(configuration.locale);
+ locationLat.setText(configuration.geolat);
+ locationLon.setText(configuration.geolon);
+ }
+
+ private void saveConfiguration(){
+ configuration.serviceKey = serviceKey.getText().toString();
+ configuration.serviceRegion = serviceRegion.getText().toString();
+ configuration.botId = botId.getText().toString();
+ configuration.voiceName = voiceName.getText().toString();
+ configuration.userId = userId.getText().toString();
+ configuration.locale = locale.getText().toString();
+ configuration.geolat = locationLat.getText().toString();
+ configuration.geolon = locationLon.getText().toString();
+ speechServiceBinder.setConfiguration(configuration);
+ }
+
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/configuration/AppConfigurationActivity.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/configuration/AppConfigurationActivity.java
new file mode 100644
index 0000000000..a191bf779b
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/configuration/AppConfigurationActivity.java
@@ -0,0 +1,85 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.activities.configuration;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.design.widget.TextInputEditText;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+
+import com.microsoft.bot.builder.solutions.directlinespeech.ConfigurationManager;
+import com.microsoft.bot.builder.solutions.directlinespeech.model.Configuration;
+import com.microsoft.bot.builder.solutions.virtualassistant.R;
+import com.microsoft.bot.builder.solutions.virtualassistant.activities.BaseActivity;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.OnEditorAction;
+
+/**
+ * App Configuration Activity - settings to change the way the app behaves
+ * Note: settings are saved when OK is pressed
+ */
+public class AppConfigurationActivity extends BaseActivity {
+
+ // VIEWS
+ @BindView(R.id.history_linecount) TextInputEditText historyLinecount;
+
+ // CONSTANTS
+ private static final int CONTENT_VIEW = R.layout.activity_app_configuration;
+
+ // STATE
+ private Configuration configuration;
+ private ConfigurationManager configurationManager;
+
+ public static Intent getNewIntent(Context context) {
+ return new Intent(context, AppConfigurationActivity.class);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(CONTENT_VIEW);
+ ButterKnife.bind(this);
+
+ configurationManager = new ConfigurationManager(this);
+ configuration = configurationManager.getConfiguration();
+
+ showConfiguration();
+ }
+
+ @OnEditorAction({R.id.history_linecount})
+ boolean onEditorAction(int actionId, KeyEvent key){
+ boolean handled = false;
+ if (actionId == EditorInfo.IME_ACTION_SEND || (key != null && key.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
+ hideKeyboardFrom(getCurrentFocus());
+ handled = true;
+ }
+ return handled;
+ }
+
+ @OnClick(R.id.btn_cancel)
+ public void onClickCancel() {
+ finish();
+ }
+
+ @OnClick(R.id.btn_ok)
+ public void onClickOk() {
+ saveConfiguration();// must save updated config first
+ finish();
+ }
+
+ private void showConfiguration(){
+ int iHistoryLinecount = configuration.historyLinecount==null?1:configuration.historyLinecount;
+ String historyLineCount = String.valueOf(iHistoryLinecount);
+ historyLinecount.setText(historyLineCount);
+ }
+
+ private void saveConfiguration(){
+ configuration.historyLinecount = Integer.valueOf(historyLinecount.getText().toString());
+ if (configuration.historyLinecount == 0) configuration.historyLinecount = 1;//do not allow 0
+ configurationManager.setConfiguration(configuration);
+ }
+
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/MainActivity.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/MainActivity.java
new file mode 100644
index 0000000000..e15cdd4249
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/MainActivity.java
@@ -0,0 +1,382 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.activities.main;
+
+import android.Manifest;
+import android.app.assist.AssistContent;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+import android.support.design.widget.NavigationView;
+import android.support.design.widget.TextInputEditText;
+import android.support.design.widget.TextInputLayout;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SwitchCompat;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.microsoft.bot.builder.solutions.directlinespeech.ConfigurationManager;
+import com.microsoft.bot.builder.solutions.directlinespeech.model.Configuration;
+import com.microsoft.bot.builder.solutions.virtualassistant.R;
+import com.microsoft.bot.builder.solutions.virtualassistant.activities.BaseActivity;
+import com.microsoft.bot.builder.solutions.virtualassistant.activities.botconfiguration.BotConfigurationActivity;
+import com.microsoft.bot.builder.solutions.virtualassistant.activities.configuration.AppConfigurationActivity;
+import com.microsoft.bot.builder.solutions.virtualassistant.activities.main.list.ChatAdapter;
+import com.microsoft.bot.builder.solutions.virtualassistant.activities.main.list.ItemOffsetDecoration;
+import com.microsoft.bot.builder.solutions.virtualassistant.assistant.VoiceInteractionActivity;
+
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnCheckedChanged;
+import butterknife.OnClick;
+import butterknife.OnEditorAction;
+import client.model.BotConnectorActivity;
+import events.ActivityReceived;
+import events.Disconnected;
+import events.Recognized;
+import events.RecognizedIntermediateResult;
+
+public class MainActivity extends BaseActivity implements NavigationView.OnNavigationItemSelectedListener {
+
+ // VIEWS
+ @BindView(R.id.root_container) RelativeLayout uiContainer;
+ @BindView(R.id.recyclerview) RecyclerView chatRecyclerView;
+ @BindView(R.id.textinputlayout) TextInputLayout textInputLayout;
+ @BindView(R.id.textinput) TextInputEditText textInput;
+ @BindView(R.id.drawer_layout) DrawerLayout drawer;
+ @BindView(R.id.nav_view) NavigationView navigationView;
+ @BindView(R.id.switch_show_textinput) SwitchCompat switchShowTextInput;
+ @BindView(R.id.speech_detection) TextView detectedSpeechToText;
+ @BindView(R.id.agent_image) ImageView agentImage;
+
+ // CONSTANTS
+ private static final int CONTENT_VIEW = R.layout.activity_main;
+ private static final String LOGTAG = "MainActivity";
+
+ // STATE
+ private ChatAdapter chatAdapter;
+ private boolean alwaysShowTextInput;
+ private Handler handler;
+ private boolean launchedAsAssistant;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(CONTENT_VIEW);
+ ButterKnife.bind(this);
+
+ handler = new Handler(Looper.getMainLooper());
+
+
+ // Options hidden in the nav-drawer
+ alwaysShowTextInput = getBooleanSharedPref(SHARED_PREF_SHOW_TEXTINPUT);
+ switchShowTextInput.setChecked(alwaysShowTextInput);
+
+ // NAV DRAWER
+ ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, drawer, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
+ drawer.addDrawerListener(toggle);
+ toggle.syncState();
+ navigationView.setNavigationItemSelectedListener(this);
+
+ // handle dangerous permissions
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
+ requestRecordAudioPermissions();
+ } else {
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ requestFineLocationPermissions();
+ }
+ }
+
+ // make media volume the default
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ setupChatRecyclerView();
+
+ // check if this activity was launched as an assistant
+ Intent intent = getIntent();
+ if (intent != null) {
+ String originator = intent.getStringExtra(VoiceInteractionActivity.KEY_ORIGINATOR);
+ if (originator != null && originator.equals(VoiceInteractionActivity.KEY_VALUE)) {
+ launchedAsAssistant = true;//this flag can now be used, i.e. to automtically start microphone recording
+ }
+ }
+
+ }
+
+ // Register for EventBus messages and SpeechService
+ @Override
+ public void onStart() {
+ super.onStart();
+ EventBus.getDefault().register(this);
+ if (speechServiceBinder == null) {
+ doBindService();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ final ConfigurationManager configurationManager = new ConfigurationManager(this);
+ final Configuration configuration = configurationManager.getConfiguration();
+ chatAdapter.setChatItemHistoryCount(configuration.historyLinecount==null?1:configuration.historyLinecount);
+ }
+
+ // Unregister EventBus messages and SpeechService
+ @Override
+ public void onStop() {
+ Log.v("BaseActivity","onStop() finished");
+ EventBus.getDefault().unregister(this);
+ if (speechServiceBinder != null) {
+ unbindService(myConnection);
+ speechServiceBinder = null;
+ }
+ super.onStop();
+ }
+
+ private void setupChatRecyclerView() {
+
+ chatAdapter = new ChatAdapter();
+ chatRecyclerView.setAdapter(chatAdapter);
+
+ LinearLayoutManager layoutManager = new LinearLayoutManager(this);
+ layoutManager.setStackFromEnd(true);
+ chatRecyclerView.setLayoutManager(layoutManager);
+
+ final int spacing = getResources().getDimensionPixelOffset(R.dimen.list_item_spacing_small);
+ chatRecyclerView.addItemDecoration(new ItemOffsetDecoration(spacing));
+ }
+
+ @Override
+ protected void permissionDenied(String manifestPermission) {
+ if (manifestPermission.equals(Manifest.permission.RECORD_AUDIO)){
+ speechServiceBinder.initializeSpeechSdk(false);
+ speechServiceBinder.getSpeechSdk().connectAsync();
+ agentImage.setVisibility(View.GONE);//hide the assistant since voice is deactivated
+ textInputLayout.setVisibility(View.VISIBLE);// show the text-input prompt
+ }
+ }
+
+ @Override
+ protected void permissionGranted(String manifestPermission) {
+ // this code is triggered when a user launches app a 1st time and doesn't have permisison yet
+ if (manifestPermission.equals(Manifest.permission.RECORD_AUDIO)){
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ requestFineLocationPermissions();
+ } else {
+ initializeAndConnect();
+ }
+ }
+ if (manifestPermission.equals(Manifest.permission.ACCESS_FINE_LOCATION)){
+ if (speechServiceBinder != null) speechServiceBinder.startLocationUpdates();
+ initializeAndConnect();
+ }
+ }
+
+ @Override
+ protected void serviceConnected() {
+ // this code is triggered when a user launches the app a second+ time and the app has permission
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
+ initializeAndConnect();
+ }
+ }
+
+ @Override
+ public boolean onNavigationItemSelected(MenuItem item) {
+ int id = item.getItemId();
+
+ switch (id) {
+ case R.id.nav_menu_configuration:
+ startActivity(BotConfigurationActivity.getNewIntent(this));
+ break;
+ case R.id.nav_menu_app_configuration:
+ startActivity(AppConfigurationActivity.getNewIntent(this));
+ break;
+ case R.id.nav_menu_reset_bot:
+ speechServiceBinder.getSpeechSdk().resetBot();
+ break;
+ case R.id.nav_menu_location:
+ Configuration configuration = speechServiceBinder.getConfiguration();
+ speechServiceBinder.getSpeechSdk().sendLocationEvent(configuration.geolat, configuration.geolon);
+ break;
+ case R.id.nav_menu_welcome_req:
+ speechServiceBinder.getSpeechSdk().requestWelcomeCard();
+ break;
+ case R.id.nav_menu_emulate_activity_msg:
+ final String testJson =
+ "{\"attachmentLayout\":\"carousel\",\"attachments\":[{\"content\":{\"body\":[{\"items\":[{\"columns\":[{\"items\":[{\"color\":\"accent\",\"id\":\"Name\",\"separation\":\"none\",\"size\":\"large\",\"spacing\":\"none\",\"text\":\"City Center Plaza\",\"type\":\"TextBlock\",\"weight\":\"bolder\"},{\"id\":\"AvailableDetails\",\"isSubtle\":true,\"separation\":\"none\",\"spacing\":\"none\",\"text\":\"Parking Garage\",\"type\":\"TextBlock\"},{\"color\":\"dark\",\"id\":\"Address\",\"isSubtle\":true,\"separation\":\"none\",\"spacing\":\"none\",\"text\":\"474 108th Avenue Northeast, Bellevue, West Bellevue\",\"type\":\"TextBlock\",\"wrap\":true},{\"color\":\"dark\",\"id\":\"Hours\",\"isSubtle\":true,\"separation\":\"none\",\"spacing\":\"none\",\"text\":\"\",\"type\":\"TextBlock\",\"wrap\":true}],\"type\":\"Column\",\"verticalContentAlignment\":\"Center\",\"width\":\"90\"}],\"type\":\"ColumnSet\"}],\"type\":\"Container\"},{\"items\":[{\"id\":\"Image\",\"type\":\"Image\",\"url\":\"https://atlas.microsoft.com/map/static/png?api-version=1.0&layer=basic&style=main&zoom=15¢er=-122.19475,47.61426&width=512&height=512&subscription-key=X0_-LfxI-A-iXxsBGb62ZZJfdfr5mbw9LiG8-cL6quM\"}],\"separator\":true,\"type\":\"Container\"}],\"id\":\"PointOfInterestViewCard\",\"speak\":\"City Center Plaza at 474 108th Avenue Northeast\",\"type\":\"AdaptiveCard\",\"version\":\"1.0\"},\"contentType\":\"application/vnd.microsoft.card.adaptive\"},{\"content\":{\"body\":[{\"items\":[{\"columns\":[{\"items\":[{\"color\":\"accent\",\"id\":\"Name\",\"separation\":\"none\",\"size\":\"large\",\"spacing\":\"none\",\"text\":\"Plaza Center\",\"type\":\"TextBlock\",\"weight\":\"bolder\"},{\"id\":\"AvailableDetails\",\"isSubtle\":true,\"separation\":\"none\",\"spacing\":\"none\",\"text\":\"Parking Garage\",\"type\":\"TextBlock\"},{\"color\":\"dark\",\"id\":\"Address\",\"isSubtle\":true,\"separation\":\"none\",\"spacing\":\"none\",\"text\":\"10901 NE 9th St, Bellevue, Northwest Bellevue\",\"type\":\"TextBlock\",\"wrap\":true},{\"color\":\"dark\",\"id\":\"Hours\",\"isSubtle\":true,\"separation\":\"none\",\"spacing\":\"none\",\"text\":\"\",\"type\":\"TextBlock\",\"wrap\":true}],\"type\":\"Column\",\"verticalContentAlignment\":\"Center\",\"width\":\"90\"}],\"type\":\"ColumnSet\"}],\"type\":\"Container\"},{\"items\":[{\"id\":\"Image\",\"type\":\"Image\",\"url\":\"https://atlas.microsoft.com/map/static/png?api-version=1.0&layer=basic&style=main&zoom=15¢er=-122.19493,47.61793&width=512&height=512&subscription-key=X0_-LfxI-A-iXxsBGb62ZZJfdfr5mbw9LiG8-cL6quM\"}],\"separator\":true,\"type\":\"Container\"}],\"id\":\"PointOfInterestViewCard\",\"speak\":\"Plaza Center at 10901 NE 9th St\",\"type\":\"AdaptiveCard\",\"version\":\"1.0\"},\"contentType\":\"application/vnd.microsoft.card.adaptive\"}],\"channelData\":{\"conversationalAiData\":{\"requestInfo\":{\"interactionId\":\"b9ad8f12-e459-4a73-a542-919224e83b0a\",\"requestType\":0,\"version\":\"0.2\"}}},\"channelId\":\"directlinespeech\",\"conversation\":{\"id\":\"490b89e7-ab99-4ec6-b0c8-4cc612d5e4ce\",\"isGroup\":false},\"entities\":[],\"from\":{\"id\":\"vakonadj\"},\"id\":\"a27c2f8da5a845a3942f6a880562114f\",\"inputHint\":\"expectingInput\",\"recipient\":{\"id\":\"490b89e7-ab99-4ec6-b0c8-4cc612d5e4ce|0000\"},\"replyToId\":\"c3174265-3b0a-49a1-bdb1-e55c477b8c36\",\"serviceUrl\":\"PersistentConnection\",\"speak\":\"What do you think of these?,1 - City Center Plaza at 474 108th Avenue Northeast.,2 - Plaza Center at 10901 NE 9th St.\",\"text\":\"What do you think of these?\",\"timestamp\":\"2019-04-25T18:17:12.3964213+00:00\",\"type\":\"message\"}";
+ speechServiceBinder.getSpeechSdk().activityReceived(testJson);
+ break;
+ case R.id.nav_menu_show_assistant_settings:
+ startActivity(new Intent(Settings.ACTION_VOICE_INPUT_SETTINGS));
+ break;
+ }
+
+ drawer.closeDrawer(GravityCompat.START);
+
+ return true;
+ }
+
+ @OnClick(R.id.agent_image)
+ public void onAssistantClick() {
+ showSnackbar(uiContainer, getString(R.string.msg_listening));
+ speechServiceBinder.getSpeechSdk().listenOnceAsync();
+ }
+
+ @OnCheckedChanged(R.id.switch_show_textinput)
+ public void OnShowTextInput(CompoundButton button, boolean checked){
+ alwaysShowTextInput = checked;
+ putBooleanSharedPref(SHARED_PREF_SHOW_TEXTINPUT, checked);
+ if (alwaysShowTextInput)
+ textInputLayout.setVisibility(View.VISIBLE);
+ else
+ textInputLayout.setVisibility(View.GONE);
+ }
+
+ @OnEditorAction(R.id.textinput)
+ boolean onEditorAction(int actionId, KeyEvent key){
+ boolean handled = false;
+ if (actionId == EditorInfo.IME_ACTION_SEND || (key != null && key.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
+ sendTextMessage(textInput.getEditableText().toString());
+ textInput.setText("");
+ hideKeyboardFrom(textInput);
+ handled = true;
+ }
+ return handled;
+ }
+
+ // send text message
+ private void sendTextMessage(String msg){
+ speechServiceBinder.getSpeechSdk().sendActivityMessageAsync(msg);
+ }
+
+ // EventBus: the user spoke and the app recognized intermediate speech
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onDisconnectedEvent(Disconnected event) {
+ detectedSpeechToText.setText(R.string.msg_disconnected);
+ boolean havePermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
+ speechServiceBinder.initializeSpeechSdk(havePermission);
+ speechServiceBinder.getSpeechSdk().connectAsync();
+ handler.postDelayed(() -> {
+ detectedSpeechToText.setText("");
+ }, 2000);
+ }
+
+ // EventBus: the user spoke and the app recognized intermediate speech
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onRecognizedIntermediateResultEvent(RecognizedIntermediateResult event) {
+ detectedSpeechToText.setText(event.recognized_speech);
+ }
+
+ // EventBus: the user spoke and the app recognized the speech. Disconnect mic.
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onRecognizedEvent(Recognized event) {
+ detectedSpeechToText.setText(event.recognized_speech);
+ // in 2 seconds clear the text (at this point the bot should be giving its' response)
+ handler.postDelayed(() -> detectedSpeechToText.setText(""), 2000);
+ }
+
+ // EventBus: received a response from Bot
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onActivityReceivedEvent(ActivityReceived activityReceived) throws IOException {
+ if (activityReceived.botConnectorActivity != null) {
+ BotConnectorActivity botConnectorActivity = activityReceived.botConnectorActivity;
+
+ String amount;
+
+ switch (botConnectorActivity.getType()) {
+ case "message":
+ chatAdapter.addChat(botConnectorActivity, this);
+ // make the chat list scroll automatically after adding a bot response
+ chatRecyclerView.getLayoutManager().scrollToPosition(chatAdapter.getItemCount() - 1);
+ break;
+ case "dialogState":
+ Log.i(LOGTAG, "Activity with DialogState");
+ break;
+ case "PlayLocalFile":
+ Log.i(LOGTAG, "Activity with PlayLocalFile");
+ playMediaStream(botConnectorActivity.getFile());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private void playMediaStream(String mediaStream) {
+ try {
+ MediaPlayer mediaPlayer = new MediaPlayer();
+ mediaPlayer.setDataSource(mediaStream);
+ mediaPlayer.prepare();
+ mediaPlayer.start();
+ }
+ catch(IOException e) {
+ Log.e(LOGTAG, "IOexception " + e.getMessage());
+ }
+
+ }
+
+ // provide additional data to the Assistant
+ // improve the assistant user experience by providing content-related references related to the current activity
+ @Override
+ public void onProvideAssistContent(AssistContent outContent) {
+ super.onProvideAssistContent(outContent);
+ //NOTE: to test the data you're passing in, see https://search.google.com/structured-data/testing-tool/u/0/
+ // ALSO: see https://schema.org/ for the vocabulary
+
+ // three examples :
+// outContent.setWebUri(
+// Uri.parse(
+// "http://www.goodreads.com/book/show/13023.Alice_in_Wonderland"
+// )
+// );
+// outContent.setStructuredData(
+// new JSONObject()
+// .put("@type", "Book")
+// .put("author", "Lewis Carroll")
+// .put("name", "Alice in Wonderland")
+// .put("description",
+// "This is an 1865 novel about a girl named Alice, " +
+// "who falls through a rabbit hole and " +
+// "enters a fantasy world."
+// ).toString()
+// );
+
+ try {
+ String structuredJson = new JSONObject()
+ .put("@type", "MusicRecording")
+ .put("@id", "https://example.com/music/recording")
+ .put("name", "Album Title")
+ .put("description", "Album Description")
+ .toString();
+
+ outContent.setStructuredData(structuredJson);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ }
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/list/ActionHandler.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/list/ActionHandler.java
new file mode 100644
index 0000000000..de55f9b272
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/list/ActionHandler.java
@@ -0,0 +1,23 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.activities.main.list;
+
+import io.adaptivecards.objectmodel.BaseActionElement;
+import io.adaptivecards.objectmodel.BaseCardElement;
+import io.adaptivecards.renderer.RenderedAdaptiveCard;
+import io.adaptivecards.renderer.actionhandler.ICardActionHandler;
+
+public class ActionHandler implements ICardActionHandler {
+ @Override
+ public void onAction(BaseActionElement baseActionElement, RenderedAdaptiveCard renderedAdaptiveCard) {
+
+ }
+
+ @Override
+ public void onMediaPlay(BaseCardElement baseCardElement, RenderedAdaptiveCard renderedAdaptiveCard) {
+
+ }
+
+ @Override
+ public void onMediaStop(BaseCardElement baseCardElement, RenderedAdaptiveCard renderedAdaptiveCard) {
+
+ }
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/list/ChatAdapter.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/list/ChatAdapter.java
new file mode 100644
index 0000000000..cbe5664f36
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/list/ChatAdapter.java
@@ -0,0 +1,72 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.activities.main.list;
+
+import android.support.annotation.NonNull;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.microsoft.bot.builder.solutions.virtualassistant.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import client.model.BotConnectorActivity;
+
+public class ChatAdapter extends RecyclerView.Adapter {
+
+ // CONSTANTS
+ private final int CONTENT_VIEW = R.layout.item_chat;
+ private static final String LOGTAG = "ChatAdapter";
+
+ // STATE
+ private ArrayList chatList = new ArrayList<>();
+ private AppCompatActivity parentActivity;
+ private static int MAX_CHAT_ITEMS = 1;
+
+
+ @NonNull
+ @Override
+ public ChatViewholder onCreateViewHolder(@NonNull ViewGroup parent, int i) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(CONTENT_VIEW, parent, false);
+ return new ChatViewholder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ChatViewholder chatViewholder, int position) {
+ BotConnectorActivity botConnectorActivity = chatList.get(position);
+ chatViewholder.bind(botConnectorActivity, parentActivity);
+ }
+
+ @Override
+ public int getItemCount() {
+ if (chatList == null) return 0;
+ return chatList.size();
+ }
+
+ public void addChat(BotConnectorActivity botConnectorActivity, AppCompatActivity parentActivity) {
+ Log.v(LOGTAG, "showing row id "+ botConnectorActivity.getId());
+ this.parentActivity = parentActivity;
+ chatList.add(botConnectorActivity);
+ if (chatList.size() > MAX_CHAT_ITEMS) {
+ chatList.remove(0);
+ }
+ notifyDataSetChanged();
+ }
+
+ public void setChatItemHistoryCount(int count){
+ MAX_CHAT_ITEMS = count;
+ while (chatList.size() > MAX_CHAT_ITEMS) {
+ chatList.remove(0);
+ }
+ notifyDataSetChanged();
+ }
+
+ public void swapChatList(List newChatList) {
+ chatList.clear();
+ chatList.addAll(newChatList);
+ notifyDataSetChanged();
+ }
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/list/ChatViewholder.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/list/ChatViewholder.java
new file mode 100644
index 0000000000..4caf4f3cc3
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/list/ChatViewholder.java
@@ -0,0 +1,106 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.activities.main.list;
+
+import android.support.annotation.NonNull;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.CardView;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.google.gson.Gson;
+import com.microsoft.bot.builder.solutions.virtualassistant.R;
+import com.microsoft.bot.builder.solutions.virtualassistant.utils.RawUtils;
+
+import org.json.JSONObject;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import client.model.BotConnectorActivity;
+import io.adaptivecards.objectmodel.AdaptiveCard;
+import io.adaptivecards.objectmodel.HostConfig;
+import io.adaptivecards.objectmodel.ParseResult;
+import io.adaptivecards.renderer.AdaptiveCardRenderer;
+import io.adaptivecards.renderer.RenderedAdaptiveCard;
+
+public class ChatViewholder extends RecyclerView.ViewHolder {
+
+ // CONSTANTS
+ private static final String LOGTAG = "ChatViewholder";
+
+ // VIEWS
+ @BindView(R.id.tv_bot_chat) TextView textMessage;
+ @BindView(R.id.adaptive_card_container) LinearLayout adaptiveCardLayout;
+
+ // STATE
+ private View view;
+ private ActionHandler cardActionHandler;
+
+ public ChatViewholder(@NonNull View itemView) {
+ super(itemView);
+ view = itemView;
+ ButterKnife.bind(this, view);
+ cardActionHandler = new ActionHandler();
+ }
+
+ /**
+ * bind the layout with the data
+ * @param botConnectorActivity data
+ */
+ void bind(@NonNull BotConnectorActivity botConnectorActivity, AppCompatActivity parentActivity) {
+ textMessage.setText(botConnectorActivity.getText());
+
+ if (botConnectorActivity.getAttachmentLayout() != null){
+ if (botConnectorActivity.getAttachmentLayout().equals("carousel") || botConnectorActivity.getAttachmentLayout().equals("list")){
+ Gson gson = new Gson();
+
+ adaptiveCardLayout.setVisibility(View.VISIBLE);
+ adaptiveCardLayout.removeAllViews();
+
+ // generate horizontal or vertical carousel of cards
+ for (int x = 0; x < botConnectorActivity.getAttachments().size(); x++) {
+ String cardJson = gson.toJson(botConnectorActivity.getAttachments().get(x));
+
+ try {
+ JSONObject cardJsonObject = new JSONObject(cardJson);
+ String cardBodyJson = cardJsonObject.getString("content");
+ logLargeString("Received Card: " + cardBodyJson);// this JSON can be used with https://adaptivecards.io/designer/
+
+ ParseResult parseResult = AdaptiveCard.DeserializeFromString(cardBodyJson, AdaptiveCardRenderer.VERSION);
+ AdaptiveCard adaptiveCard = parseResult.GetAdaptiveCard();
+ HostConfig hostConfig = HostConfig.DeserializeFromString(RawUtils.loadHostConfig(parentActivity));
+ RenderedAdaptiveCard renderedCard = AdaptiveCardRenderer.getInstance().render(parentActivity, parentActivity.getSupportFragmentManager(), adaptiveCard, cardActionHandler, hostConfig);
+
+ View adaptiveCardRendered = renderedCard.getView();
+
+ // add the card to its individual container to allow for resizing
+ View adaptiveCardView = LayoutInflater.from(parentActivity).inflate(R.layout.item_adaptive_card, adaptiveCardLayout, false);
+ CardView itemAdaptiveCard = adaptiveCardView.findViewById(R.id.adaptive_card_container);
+ itemAdaptiveCard.addView(adaptiveCardRendered);
+
+ // add the cards to the existing layout to make them visible
+ adaptiveCardLayout.addView(itemAdaptiveCard);
+
+ Log.d("ActionHandler", "renderedAdaptiveCard warnings: "+renderedCard.getWarnings().size() + " " + renderedCard.getWarnings().toString());
+ }
+ catch (Exception ex) {
+ Log.e(LOGTAG,"Error in json: " + ex.getMessage());
+ }
+ }
+ }
+ } else {
+ adaptiveCardLayout.setVisibility(View.GONE);
+ }
+ }
+
+ public void logLargeString(String str) {
+ if(str.length() > 3000) {
+ Log.i(LOGTAG, str.substring(0, 3000));
+ logLargeString(str.substring(3000));
+ } else {
+ Log.i(LOGTAG, str); // continuation
+ }
+ }
+}
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/list/ItemOffsetDecoration.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/list/ItemOffsetDecoration.java
new file mode 100644
index 0000000000..9d842d0530
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/activities/main/list/ItemOffsetDecoration.java
@@ -0,0 +1,20 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.activities.main.list;
+
+import android.graphics.Rect;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+public class ItemOffsetDecoration extends RecyclerView.ItemDecoration {
+
+ private int mSpacing;
+
+ public ItemOffsetDecoration(int itemOffset) {
+ mSpacing = itemOffset;
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
+ super.getItemOffsets(outRect, view, parent, state);
+ outRect.set(mSpacing, mSpacing, mSpacing, mSpacing);
+ }
+}
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/AssistantRecognitionService.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/AssistantRecognitionService.java
new file mode 100644
index 0000000000..ae0798492a
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/AssistantRecognitionService.java
@@ -0,0 +1,41 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.assistant;
+
+import android.content.Intent;
+import android.speech.RecognitionService;
+import android.util.Log;
+
+/**
+ * Stub recognition service needed to be a complete voice interactor.
+ */
+public class AssistantRecognitionService extends RecognitionService {
+
+ private static final String LOGTAG = "AssistantRecognitionSvc";
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.i(LOGTAG, "onCreate");
+ }
+
+ @Override
+ protected void onStartListening(Intent recognizerIntent, Callback listener) {
+ Log.i(LOGTAG, "onStartListening");
+ }
+
+ @Override
+ protected void onCancel(Callback listener) {
+ Log.i(LOGTAG, "onCancel");
+ }
+
+ @Override
+ protected void onStopListening(Callback listener) {
+ Log.i(LOGTAG, "onStopListening");
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.i(LOGTAG, "onDestroy");
+ }
+
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/AssistantService.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/AssistantService.java
new file mode 100644
index 0000000000..0902afe24b
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/AssistantService.java
@@ -0,0 +1,6 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.assistant;
+
+import android.service.voice.VoiceInteractionService;
+
+public class AssistantService extends VoiceInteractionService {
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/AssistantSession.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/AssistantSession.java
new file mode 100644
index 0000000000..acf835a549
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/AssistantSession.java
@@ -0,0 +1,72 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.assistant;
+
+import android.app.assist.AssistContent;
+import android.app.assist.AssistStructure;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionSession;
+import android.util.Log;
+
+public class AssistantSession extends VoiceInteractionSession {
+
+ // CONSTANTS
+ static final String LOGTAG = "AssistantSession";
+
+ // STATE
+ private Context context;
+
+ public AssistantSession(Context context) {
+ super(context);
+ this.context = context;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.i(LOGTAG, "onCreate");
+ }
+
+ @Override
+ public void onShow(Bundle args, int showFlags) {
+ super.onShow(args, showFlags);
+ Log.i(LOGTAG, "onShow: flags=0x" + Integer.toHexString(showFlags) + " args=" + args);
+ }
+
+ @Override
+ public void onHandleAssist(Bundle data, AssistStructure structure, AssistContent content) {
+ super.onHandleAssist(data, structure, content);
+
+ Log.i(LOGTAG, "onHandleAssist");
+ logAssistContentAndData(content, data);
+
+ Intent intent = new Intent(context, VoiceInteractionActivity.class);
+ startVoiceActivity(intent);
+ }
+
+ private void logAssistContentAndData(AssistContent content, Bundle data) {
+ if (content != null) {
+ Log.i(LOGTAG, "Assist intent: " + content.getIntent());
+ Log.i(LOGTAG, "Assist intent from app: " + content.isAppProvidedIntent());
+ Log.i(LOGTAG, "Assist clipdata: " + content.getClipData());
+ Log.i(LOGTAG, "Assist structured data: " + content.getStructuredData());
+ Log.i(LOGTAG, "Assist web uri: " + content.getWebUri());
+ Log.i(LOGTAG, "Assist web uri from app: " + content.isAppProvidedWebUri());
+ Log.i(LOGTAG, "Assist extras: " + content.getExtras());
+ }
+ if (data != null) {
+ Uri referrer = data.getParcelable(Intent.EXTRA_REFERRER);
+ if (referrer != null) {
+ Log.i(LOGTAG, "Referrer: " + referrer);
+ }
+ }
+ }
+
+ @Override
+ public void onHandleScreenshot(Bitmap screenshot) {
+ // this can provide a screenshot of the UI - check for null
+ }
+
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/AssistantSessionService.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/AssistantSessionService.java
new file mode 100644
index 0000000000..10962abbfc
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/AssistantSessionService.java
@@ -0,0 +1,13 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.assistant;
+
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionSession;
+import android.service.voice.VoiceInteractionSessionService;
+
+public class AssistantSessionService extends VoiceInteractionSessionService {
+
+ @Override
+ public VoiceInteractionSession onNewSession(Bundle args) {
+ return(new AssistantSession(this));
+ }
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/VoiceInteractionActivity.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/VoiceInteractionActivity.java
new file mode 100644
index 0000000000..0f15238e8b
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/assistant/VoiceInteractionActivity.java
@@ -0,0 +1,31 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.assistant;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+
+import com.microsoft.bot.builder.solutions.virtualassistant.activities.main.MainActivity;
+
+/**
+ * this class is used to re-launch the LAUNCH activity
+ * Also, a special flag is added to let the MainActivity identify it's launched as an assistant
+ */
+public class VoiceInteractionActivity extends AppCompatActivity {
+
+ // CONSTANTS
+ public static final String KEY_ORIGINATOR = "KEY_ORIGINATOR";
+ public static final String KEY_VALUE = "VoiceInteractionActivityFlag";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.setComponent(new ComponentName(this, MainActivity.class));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(KEY_ORIGINATOR, KEY_VALUE);//special flag
+ startActivity(intent);
+ }
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/service/LocationProvider.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/service/LocationProvider.java
new file mode 100644
index 0000000000..4774e98f9d
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/service/LocationProvider.java
@@ -0,0 +1,105 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.service;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.Location;
+import android.os.Looper;
+import android.support.v4.app.ActivityCompat;
+import android.util.Log;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GoogleApiAvailability;
+import com.google.android.gms.location.FusedLocationProviderClient;
+import com.google.android.gms.location.LocationCallback;
+import com.google.android.gms.location.LocationRequest;
+import com.google.android.gms.location.LocationResult;
+import com.google.android.gms.location.LocationServices;
+import com.google.android.gms.location.LocationSettingsRequest;
+import com.google.android.gms.location.SettingsClient;
+
+
+public class LocationProvider {
+
+ // CONSTANTS
+ private static final int LOCATION_UPDATE_INTERVAL = 3 * 60 * 1000;// 3 mins
+ private static final float LOCATION_UPDATE_DISTANCE = 50;// distance (in meters) to move before receiving an update
+ private static final String LOGTAG = "LocationProvider";
+
+ // STATE
+ private boolean isGettingLocationUpdates;
+ private FusedLocationProviderClient fusedLocationClient;
+ private LocationCallback locationCallback;
+ private Context context;
+ private LocationProviderCallback locationProviderCallback;
+
+ // INTERFACE
+ public interface LocationProviderCallback {
+ void onLocationResult(Location location);
+ }
+
+ public LocationProvider(Context context, LocationProviderCallback locationProviderCallback) {
+ this.context = context;
+ this.locationProviderCallback = locationProviderCallback;
+ // check if Google Play Services is installed
+ isGettingLocationUpdates = checkPlayServices();
+ }
+
+ protected void startLocationUpdates() {
+ if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+
+ fusedLocationClient = LocationServices.getFusedLocationProviderClient(context);
+
+ // register to receive future location updates
+ LocationRequest locationRequest = new LocationRequest()
+ .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
+ .setInterval(LOCATION_UPDATE_INTERVAL)
+ .setSmallestDisplacement(LOCATION_UPDATE_DISTANCE);
+
+ // Create LocationSettingsRequest object using location request
+ LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder();
+ builder.addLocationRequest(locationRequest);
+ LocationSettingsRequest locationSettingsRequest = builder.build();
+
+ // Check whether location settings are satisfied
+ // https://developers.google.com/android/reference/com/google/android/gms/location/SettingsClient
+ SettingsClient settingsClient = LocationServices.getSettingsClient(context);
+ settingsClient.checkLocationSettings(locationSettingsRequest);
+ locationCallback = new LocationCallback() {
+ @Override
+ public void onLocationResult(LocationResult locationResult) {
+ // Inform the Bot of the location change
+ Location location = locationResult.getLastLocation();
+ if (location != null && locationProviderCallback != null) {
+ locationProviderCallback.onLocationResult(location);
+ }
+ }
+ };
+
+ fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.myLooper());
+ } else {
+ //this will trigger the first time the app is run - just retry after getting permission
+ Log.e(LOGTAG, "Missing ACCESS_FINE_LOCATION permission");
+ }
+ }
+
+ public void stopLocationUpdates(){
+ // stop location updates
+ if (isGettingLocationUpdates && fusedLocationClient != null && locationCallback != null) {
+ fusedLocationClient.removeLocationUpdates(locationCallback);
+ }
+ }
+
+ private boolean checkPlayServices() {
+ GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
+ int resultCode = apiAvailability.isGooglePlayServicesAvailable(context);
+ if (resultCode != ConnectionResult.SUCCESS) {
+ if (apiAvailability.isUserResolvableError(resultCode)) {
+ apiAvailability.showErrorNotification(context, resultCode);
+ }
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/service/ServiceBinder.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/service/ServiceBinder.java
new file mode 100644
index 0000000000..fa6cf90e9a
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/service/ServiceBinder.java
@@ -0,0 +1,16 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.service;
+
+import android.os.Binder;
+
+public class ServiceBinder extends Binder {
+
+ private SpeechService speechService;
+
+ public ServiceBinder(SpeechService speechService) {
+ this.speechService = speechService;
+ }
+
+ public SpeechService getSpeechService() {
+ return speechService;
+ }
+}
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/service/SpeechService.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/service/SpeechService.java
new file mode 100644
index 0000000000..20c9d65f17
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/service/SpeechService.java
@@ -0,0 +1,338 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.service;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.media.MediaPlayer;
+import android.os.Build;
+import android.os.IBinder;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import com.microsoft.bot.builder.solutions.directlinespeech.ConfigurationManager;
+import com.microsoft.bot.builder.solutions.directlinespeech.SpeechSdk;
+import com.microsoft.bot.builder.solutions.directlinespeech.model.Configuration;
+import com.microsoft.bot.builder.solutions.virtualassistant.R;
+import com.microsoft.bot.builder.solutions.virtualassistant.activities.configuration.DefaultConfiguration;
+import com.microsoft.bot.builder.solutions.virtualassistant.widgets.WidgetBotRequest;
+import com.microsoft.bot.builder.solutions.virtualassistant.widgets.WidgetBotResponse;
+
+import org.greenrobot.eventbus.EventBus;
+import org.greenrobot.eventbus.Subscribe;
+import org.greenrobot.eventbus.ThreadMode;
+
+import java.io.File;
+import java.io.IOException;
+
+import client.model.BotConnectorActivity;
+import events.ActivityReceived;
+import events.Recognized;
+import events.RecognizedIntermediateResult;
+
+/**
+ * The SpeechService is the connection between bot and activities and widgets
+ * The SpeechService should always be running in the background, ready to interact with the widgets
+ * and always ready to process data immediately.
+ * Running this service as a ForegroundService best suits this requirement.
+ *
+ * NOTE: the service assumes that it has permission to RECORD_AUDIO
+ */
+public class SpeechService extends Service {
+
+ // CONSTANTS
+ private static final String TAG_FOREGROUND_SERVICE = "SpeechService";
+ public static final String ACTION_START_FOREGROUND_SERVICE = "ACTION_START_FOREGROUND_SERVICE";
+ public static final String ACTION_STOP_FOREGROUND_SERVICE = "ACTION_STOP_FOREGROUND_SERVICE";
+ public static final String ACTION_START_LISTENING = "ACTION_START_LISTENING";
+
+ // STATE
+ private IBinder binder;
+ private SpeechSdk speechSdk;
+ private ConfigurationManager configurationManager;
+ private LocationProvider locationProvider;
+
+ // CONSTRUCTOR
+ public SpeechService() {
+ binder = new ServiceBinder(this);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return binder;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ Log.d(TAG_FOREGROUND_SERVICE, "onCreate()");
+ EventBus.getDefault().register(this);
+
+ // set up configuration for SpeechSdk
+ configurationManager = new ConfigurationManager(this);
+
+ Configuration configuration = configurationManager.getConfiguration();
+ if (configuration.isEmpty()){
+ // set up defaults
+ configuration.serviceKey = DefaultConfiguration.COGNITIVE_SERVICES_SUBSCRIPTION_KEY;
+ configuration.botId = DefaultConfiguration.BOT_ID;
+ configuration.serviceRegion = DefaultConfiguration.SPEECH_REGION;
+ configuration.voiceName = DefaultConfiguration.VOICE_NAME;
+ configuration.directlineConstant = DefaultConfiguration.DIRECT_LINE_CONSTANT;
+ configuration.userName = DefaultConfiguration.USER_NAME;
+ configuration.userId = DefaultConfiguration.USER_ID;
+ configuration.locale = DefaultConfiguration.LOCALE;
+ configuration.geolat = DefaultConfiguration.GEOLOCATION_LAT;
+ configuration.geolon = DefaultConfiguration.GEOLOCATION_LON;
+ configuration.historyLinecount = Integer.MAX_VALUE - 1;
+ configurationManager.setConfiguration(configuration);
+ }
+
+ locationProvider = new LocationProvider(this, location -> {
+ final String locLat = String.valueOf(location.getLatitude());
+ final String locLon = String.valueOf(location.getLongitude());
+ speechSdk.sendLocationEvent(locLat, locLon);
+ });
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ EventBus.getDefault().unregister(this);
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if(intent != null) {
+ String action = intent.getAction();
+ if (action == null) action = ACTION_START_FOREGROUND_SERVICE;
+
+ switch (action) {
+ case ACTION_START_FOREGROUND_SERVICE:
+ Toast.makeText(getApplicationContext(), "Service is started", Toast.LENGTH_LONG).show();
+ startForegroundService();
+ break;
+ case ACTION_STOP_FOREGROUND_SERVICE:
+ Toast.makeText(getApplicationContext(), "Service is stopped", Toast.LENGTH_LONG).show();
+ stopForegroundService();
+ break;
+ case ACTION_START_LISTENING:
+ Toast.makeText(getApplicationContext(), "Listening", Toast.LENGTH_LONG).show();
+
+ if (speechSdk == null) initializeSpeechSdk(true);//assume true - for this to work the app must have been launched once for permission dialog
+ speechSdk.connectAsync();
+ speechSdk.listenOnceAsync();
+ break;
+ }
+ }
+ return START_STICKY;
+ }
+
+ private void startForegroundService() {
+ Log.d(TAG_FOREGROUND_SERVICE, "startForegroundService()");
+
+ // Create notification default intent
+ Intent intent = new Intent();
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
+
+ // Create Notification Channel
+ NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+
+ String channelId = "some_channel_id";
+
+ // Starting with API level 26
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ CharSequence channelName = "Some Channel";
+ int importance = NotificationManager.IMPORTANCE_LOW;
+ NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, importance);
+ notificationChannel.enableLights(true);
+ notificationChannel.setLightColor(Color.RED);
+ notificationChannel.enableVibration(true);
+ notificationChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 100});
+ notificationManager.createNotificationChannel(notificationChannel);
+ }
+
+ // Create notification builder
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId);
+
+ // Make notification show big text
+ NotificationCompat.BigTextStyle bigTextStyle = new NotificationCompat.BigTextStyle();
+ bigTextStyle.setBigContentTitle("Speech recognitions service");
+ bigTextStyle.bigText("Speech Service is active. Click LISTEN to speak to the Bot");
+
+ // Set big text style.
+ builder.setStyle(bigTextStyle);
+
+ // Timestamp
+ builder.setWhen(System.currentTimeMillis());
+
+ // Icon
+ builder.setSmallIcon(R.mipmap.ic_launcher);
+ Bitmap largeIconBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
+ builder.setLargeIcon(largeIconBitmap);
+
+ // Set notification priority
+ builder.setPriority(NotificationManager.IMPORTANCE_LOW);
+
+ // Make head-up notification
+ builder.setFullScreenIntent(pendingIntent, true);
+
+ // Add LISTEN button intent in notification
+ Intent listenIntent = new Intent(this, SpeechService.class);
+ listenIntent.setAction(ACTION_START_LISTENING);
+ PendingIntent pendingPlayIntent = PendingIntent.getService(this, 0, listenIntent, 0);
+ NotificationCompat.Action playAction = new NotificationCompat.Action(R.drawable.ic_mic, getString(R.string.service_notification_action_button), pendingPlayIntent);
+ builder.addAction(playAction);
+
+ // Build the notification
+ Notification notification = builder.build();
+
+ // Start foreground service
+ startForeground(1, notification);
+
+ startLocationUpdates();
+ }
+
+ private void stopForegroundService() {
+ Log.d(TAG_FOREGROUND_SERVICE, "stopForegroundService()");
+
+ locationProvider.stopLocationUpdates();
+
+ // Stop foreground service and remove the notification
+ stopForeground(true);
+
+ // Stop the foreground service
+ stopSelf();
+ }
+
+ public void startLocationUpdates() {
+ locationProvider.startLocationUpdates();
+ }
+
+ /**
+ * Initialize the speech SDK.
+ * Note: This can be called repeatedly without negative consequences, i.e. device rotation
+ * @param haveRecordAudioPermission true or false
+ */
+ public void initializeSpeechSdk(boolean haveRecordAudioPermission){
+ if (speechSdk != null) {
+ speechSdk.reset();
+ }
+ speechSdk = new SpeechSdk();
+ File directory = this.getFilesDir();
+ Configuration configuration = configurationManager.getConfiguration();
+ speechSdk.initialize(configuration, haveRecordAudioPermission, directory.getPath());
+ }
+
+ public SpeechSdk getSpeechSdk(){
+ if (speechSdk == null)
+ throw new IllegalStateException("initializeSpeechSdk() hasn't been called");
+ else
+ return speechSdk;
+ }
+
+
+ // EventBus: the user spoke and the app recognized intermediate speech
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onRecognizedIntermediateResultEvent(RecognizedIntermediateResult event) {
+ updateBotRequestWidget(event.recognized_speech);
+ }
+
+ // EventBus: the user spoke and the app recognized the speech. Disconnect mic.
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onRecognizedEvent(Recognized event) {
+ updateBotRequestWidget(event.recognized_speech);
+ }
+
+ // EventBus: received a response from Bot
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onActivityReceivedEvent(ActivityReceived activityReceived) throws IOException {
+ if (activityReceived.botConnectorActivity != null) {
+ BotConnectorActivity botConnectorActivity = activityReceived.botConnectorActivity;
+
+ String amount;
+
+ switch (botConnectorActivity.getType()) {
+ case "message":
+ updateBotResponseWidget(botConnectorActivity.getText());
+ break;
+ case "dialogState":
+ Log.i(TAG_FOREGROUND_SERVICE, "Activity with DialogState");
+ break;
+ case "PlayLocalFile":
+ Log.i(TAG_FOREGROUND_SERVICE, "Activity with PlayLocalFile");
+ playMediaStream(botConnectorActivity.getFile());
+ break;
+ case "AbsoluteTemp": {
+ // "set the cabin temp to 75F"
+ amount = botConnectorActivity.getAmount();//amount is absolute temp
+ Log.i(TAG_FOREGROUND_SERVICE, "Activity with AbsoluteTemp to "+ amount);
+ break;
+ }
+ case "DecreaseTemp": {
+ amount = botConnectorActivity.getAmount();
+ Log.i(TAG_FOREGROUND_SERVICE, "Activity with DecreaseTemp by " + amount);
+ break;
+ }
+ case "IncreaseTemp": {
+ amount = botConnectorActivity.getAmount();
+ Log.i(TAG_FOREGROUND_SERVICE, "Activity with IncreaseTemp by " + amount);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ }
+
+ private void playMediaStream(String mediaStream) {
+ try {
+ MediaPlayer mediaPlayer = new MediaPlayer();
+ mediaPlayer.setDataSource(mediaStream);
+ mediaPlayer.prepare();
+ mediaPlayer.start();
+ }
+ catch(IOException e) {
+ Log.e(TAG_FOREGROUND_SERVICE, "IOexception " + e.getMessage());
+ }
+
+ }
+
+ private void updateBotResponseWidget(String text){
+ Log.v(TAG_FOREGROUND_SERVICE, "updateBotResponseWidget("+text+")");
+ Context context = this;
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_bot_response);
+ ComponentName thisWidget = new ComponentName(context, WidgetBotResponse.class);
+ remoteViews.setTextViewText(R.id.appwidget_text, text);
+ appWidgetManager.updateAppWidget(thisWidget, remoteViews);
+ }
+
+ private void updateBotRequestWidget(String text){
+ Log.v(TAG_FOREGROUND_SERVICE, "updateBotRequestWidget("+text+")");
+ Context context = this;
+ AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
+ RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_bot_request);
+ ComponentName thisWidget = new ComponentName(context, WidgetBotRequest.class);
+ remoteViews.setTextViewText(R.id.appwidget_text, text);
+ appWidgetManager.updateAppWidget(thisWidget, remoteViews);
+ }
+
+ public Configuration getConfiguration(){
+ return configurationManager.getConfiguration();
+ }
+
+ public void setConfiguration(Configuration configuration){
+ configurationManager.setConfiguration(configuration);
+ }
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/utils/RawUtils.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/utils/RawUtils.java
new file mode 100644
index 0000000000..5039ceaf64
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/utils/RawUtils.java
@@ -0,0 +1,30 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.utils;
+
+import android.content.Context;
+
+import com.microsoft.bot.builder.solutions.virtualassistant.R;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Collection of utilities to interact with res/raw
+ */
+public class RawUtils {
+
+ public static String loadHostConfig(Context context) {
+ String json = null;
+ try {
+ InputStream is = context.getResources().openRawResource(R.raw.hostconfig);
+ int size = is.available();
+ byte[] buffer = new byte[size];
+ is.read(buffer);
+ is.close();
+ json = new String(buffer, "UTF-8");
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ return null;
+ }
+ return json;
+ }
+}
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/widgets/WidgetBotRequest.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/widgets/WidgetBotRequest.java
new file mode 100644
index 0000000000..95ae629a7d
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/widgets/WidgetBotRequest.java
@@ -0,0 +1,41 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.widgets;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.widget.RemoteViews;
+
+import com.microsoft.bot.builder.solutions.virtualassistant.R;
+
+/**
+ * Implementation of App Widget functionality.
+ */
+public class WidgetBotRequest extends AppWidgetProvider {
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ // There may be multiple widgets active, so update all of them
+ for (int appWidgetId : appWidgetIds) {
+
+ CharSequence widgetText = "";
+
+ // Construct the RemoteViews object
+ RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_bot_request);
+ views.setTextViewText(R.id.appwidget_text, widgetText);
+
+ // Instruct the widget manager to update the widget
+ appWidgetManager.updateAppWidget(appWidgetId, views);
+ }
+ }
+
+ @Override
+ public void onEnabled(Context context) {
+ // Enter relevant functionality for when the first widget is created
+ }
+
+ @Override
+ public void onDisabled(Context context) {
+ // Enter relevant functionality for when the last widget is disabled
+ }
+}
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/widgets/WidgetBotResponse.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/widgets/WidgetBotResponse.java
new file mode 100644
index 0000000000..7894a6d1ca
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/widgets/WidgetBotResponse.java
@@ -0,0 +1,41 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.widgets;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.widget.RemoteViews;
+
+import com.microsoft.bot.builder.solutions.virtualassistant.R;
+
+/**
+ * Implementation of App Widget functionality.
+ */
+public class WidgetBotResponse extends AppWidgetProvider {
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ // There may be multiple widgets active, so update all of them
+ for (int appWidgetId : appWidgetIds) {
+
+ CharSequence widgetText = "";
+
+ // Construct the RemoteViews object
+ RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_bot_response);
+ views.setTextViewText(R.id.appwidget_text, widgetText);
+
+ // Instruct the widget manager to update the widget
+ appWidgetManager.updateAppWidget(appWidgetId, views);
+ }
+ }
+
+ @Override
+ public void onEnabled(Context context) {
+ // Enter relevant functionality for when the first widget is created
+ }
+
+ @Override
+ public void onDisabled(Context context) {
+ // Enter relevant functionality for when the last widget is disabled
+ }
+}
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/widgets/WidgetTalkButton.java b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/widgets/WidgetTalkButton.java
new file mode 100644
index 0000000000..7429f9a7de
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/java/com/microsoft/bot/builder/solutions/virtualassistant/widgets/WidgetTalkButton.java
@@ -0,0 +1,58 @@
+package com.microsoft.bot.builder.solutions.virtualassistant.widgets;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import com.microsoft.bot.builder.solutions.virtualassistant.R;
+import com.microsoft.bot.builder.solutions.virtualassistant.service.SpeechService;
+
+import static com.microsoft.bot.builder.solutions.virtualassistant.service.SpeechService.ACTION_START_LISTENING;
+
+/**
+ * Implementation of App Widget functionality.
+ * This Widget launches an activity to record audio
+ */
+public class WidgetTalkButton extends AppWidgetProvider {
+
+ public static int WIDGET_LAYOUT = R.layout.widget_talk_button;
+
+ /**
+ * Updates all the widgets
+ * @param context Context
+ * @param appWidgetManager Widget Manager
+ * @param appWidgetIds Widget IDs
+ */
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ Log.d("sender", "Broadcasting message");
+ for (int appWidgetId : appWidgetIds) {
+ RemoteViews remoteView = new RemoteViews(context.getPackageName(), WIDGET_LAYOUT);
+ remoteView.setOnClickPendingIntent(R.id.push_to_talk, getPendingSelfIntent(context, appWidgetId));
+ appWidgetManager.updateAppWidget(appWidgetId, remoteView);
+ }
+
+ }
+
+ protected PendingIntent getPendingSelfIntent(Context context, int widgetId) {
+ Intent intent = new Intent(context, SpeechService.class);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
+ intent.setAction(ACTION_START_LISTENING);
+ return PendingIntent.getService(context, 0, intent, 0);
+ }
+
+ @Override
+ public void onEnabled(Context context) {
+ // Enter relevant functionality for when the first widget is created
+ }
+
+ @Override
+ public void onDisabled(Context context) {
+ // Enter relevant functionality for when the last widget is disabled
+ }
+}
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-anydpi/ic_debug.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-anydpi/ic_debug.xml
new file mode 100644
index 0000000000..eb3f0ab021
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-anydpi/ic_debug.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-anydpi/ic_welcome.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-anydpi/ic_welcome.xml
new file mode 100644
index 0000000000..c62645a9ed
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-anydpi/ic_welcome.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-hdpi/ic_debug.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-hdpi/ic_debug.png
new file mode 100644
index 0000000000..2f72ec4296
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-hdpi/ic_debug.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-hdpi/ic_gps.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-hdpi/ic_gps.png
new file mode 100644
index 0000000000..fce33da937
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-hdpi/ic_gps.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-hdpi/ic_welcome.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-hdpi/ic_welcome.png
new file mode 100644
index 0000000000..be1802040a
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-hdpi/ic_welcome.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-mdpi/ic_debug.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-mdpi/ic_debug.png
new file mode 100644
index 0000000000..871ecdabd8
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-mdpi/ic_debug.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-mdpi/ic_gps.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-mdpi/ic_gps.png
new file mode 100644
index 0000000000..8c324083e6
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-mdpi/ic_gps.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-mdpi/ic_welcome.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-mdpi/ic_welcome.png
new file mode 100644
index 0000000000..55982e555d
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-mdpi/ic_welcome.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-nodpi/widget_preview_pushtalk.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-nodpi/widget_preview_pushtalk.png
new file mode 100644
index 0000000000..2434d7dcaf
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-nodpi/widget_preview_pushtalk.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-nodpi/widget_preview_request.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-nodpi/widget_preview_request.png
new file mode 100644
index 0000000000..39bd9cbade
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-nodpi/widget_preview_request.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-nodpi/widget_preview_response.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-nodpi/widget_preview_response.png
new file mode 100644
index 0000000000..080b2775f0
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-nodpi/widget_preview_response.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000000..1f6bb29060
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xhdpi/ic_debug.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xhdpi/ic_debug.png
new file mode 100644
index 0000000000..09c8de8ec2
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xhdpi/ic_debug.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xhdpi/ic_gps.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xhdpi/ic_gps.png
new file mode 100644
index 0000000000..debe15ee34
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xhdpi/ic_gps.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xhdpi/ic_welcome.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xhdpi/ic_welcome.png
new file mode 100644
index 0000000000..d24f03e500
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xhdpi/ic_welcome.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xxhdpi/ic_debug.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xxhdpi/ic_debug.png
new file mode 100644
index 0000000000..e0510b4c11
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xxhdpi/ic_debug.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xxhdpi/ic_gps.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xxhdpi/ic_gps.png
new file mode 100644
index 0000000000..bd91bcb162
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xxhdpi/ic_gps.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xxhdpi/ic_welcome.png b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xxhdpi/ic_welcome.png
new file mode 100644
index 0000000000..fad8aac55e
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable-xxhdpi/ic_welcome.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/ic_gps.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/ic_gps.xml
new file mode 100644
index 0000000000..f8ae310ab5
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/ic_gps.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/ic_launcher_background.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000000..0d025f9bf6
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/ic_mic.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/ic_mic.xml
new file mode 100644
index 0000000000..68c4fa1365
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/ic_mic.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/shape_rect_round_corners_dark.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/shape_rect_round_corners_dark.xml
new file mode 100644
index 0000000000..96f1922027
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/shape_rect_round_corners_dark.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/shape_rect_round_corners_light_gray.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/shape_rect_round_corners_light_gray.xml
new file mode 100644
index 0000000000..1104b2eff5
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/drawable/shape_rect_round_corners_light_gray.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/layout/activity_app_configuration.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/activity_app_configuration.xml
new file mode 100644
index 0000000000..49087eda05
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/activity_app_configuration.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/layout/activity_bot_configuration.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/activity_bot_configuration.xml
new file mode 100644
index 0000000000..ba283d6738
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/activity_bot_configuration.xml
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/layout/activity_main.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000000..26521780db
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/layout/item_adaptive_card.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/item_adaptive_card.xml
new file mode 100644
index 0000000000..4a6b0b59c4
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/item_adaptive_card.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/layout/item_chat.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/item_chat.xml
new file mode 100644
index 0000000000..42ac08a512
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/item_chat.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/layout/widget_bot_request.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/widget_bot_request.xml
new file mode 100644
index 0000000000..a5b799950c
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/widget_bot_request.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/layout/widget_bot_response.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/widget_bot_response.xml
new file mode 100644
index 0000000000..a5b799950c
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/widget_bot_response.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/layout/widget_talk_button.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/widget_talk_button.xml
new file mode 100644
index 0000000000..7317f07b93
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/layout/widget_talk_button.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/menu/nav_drawer_menu.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/menu/nav_drawer_menu.xml
new file mode 100644
index 0000000000..386c60c6eb
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/menu/nav_drawer_menu.xml
@@ -0,0 +1,31 @@
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000000..eca70cfe52
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000000..eca70cfe52
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-hdpi/ic_launcher.png b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..898f3ed59a
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..dffca3601e
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-mdpi/ic_launcher.png b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..64ba76f75e
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..dae5e08234
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..e5ed46597e
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..14ed0af350
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..b0907cac3b
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..d8ae031549
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..2c18de9e66
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000..beed3cdd2c
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/raw/hostconfig.json b/solutions/android/VirtualAssistantClient/app/src/main/res/raw/hostconfig.json
new file mode 100644
index 0000000000..d053d5bfee
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/raw/hostconfig.json
@@ -0,0 +1,143 @@
+{
+ "choiceSetInputValueSeparator": ",",
+ "supportsInteractivity": true,
+ "fontFamily": "Segoe UI",
+ "spacing": {
+ "small": 3,
+ "default": 8,
+ "medium": 20,
+ "large": 30,
+ "extraLarge": 40,
+ "padding": 10
+ },
+ "separator": {
+ "lineThickness": 1,
+ "lineColor": "#EEEEEE"
+ },
+ "fontSizes": {
+ "small": 14,
+ "default": 18,
+ "medium": 21,
+ "large": 23,
+ "extraLarge": 30
+ },
+ "fontWeights": {
+ "lighter": 200,
+ "default": 400,
+ "bolder": 600
+ },
+ "imageSizes": {
+ "small": 80,
+ "medium": 160,
+ "large": 320
+ },
+ "containerStyles": {
+ "default": {
+ "foregroundColors": {
+ "default": {
+ "default": "#1b67e2",
+ "subtle": "#EE03255b"
+ },
+ "dark": {
+ "default": "#000000",
+ "subtle": "#66000000"
+ },
+ "light": {
+ "default": "#FFFFFF",
+ "subtle": "#33000000"
+ },
+ "accent": {
+ "default": "#2E89FC",
+ "subtle": "#882E89FC"
+ },
+ "good": {
+ "default": "#54a254",
+ "subtle": "#DD54a254"
+ },
+ "warning": {
+ "default": "#c3ab23",
+ "subtle": "#DDc3ab23"
+ },
+ "attention": {
+ "default": "#FF0000",
+ "subtle": "#DDFF0000"
+ }
+ },
+ "backgroundColor": "#00FFFFFF"
+ },
+ "emphasis": {
+ "foregroundColors": {
+ "default": {
+ "default": "#333333",
+ "subtle": "#EE333333"
+ },
+ "dark": {
+ "default": "#000000",
+ "subtle": "#66000000"
+ },
+ "light": {
+ "default": "#FFFFFF",
+ "subtle": "#33000000"
+ },
+ "accent": {
+ "default": "#2E89FC",
+ "subtle": "#882E89FC"
+ },
+ "good": {
+ "default": "#54a254",
+ "subtle": "#DD54a254"
+ },
+ "warning": {
+ "default": "#c3ab23",
+ "subtle": "#DDc3ab23"
+ },
+ "attention": {
+ "default": "#FF0000",
+ "subtle": "#DDFF0000"
+ }
+ },
+ "backgroundColor": "#08FFFFFF"
+ }
+ },
+ "actions": {
+ "maxActions": 5,
+ "spacing": "Default",
+ "buttonSpacing": 10,
+ "showCard": {
+ "actionMode": "Inline",
+ "inlineTopMargin": 16,
+ "style": "emphasis"
+ },
+ "preExpandSingleShowCardAction": false,
+ "actionsOrientation": "Horizontal",
+ "actionAlignment": "Center"
+ },
+ "adaptiveCard": {
+ "allowCustomStyle": true
+ },
+ "imageSet": {
+ "imageSize": "Stretch",
+ "maxImageHeight": 100
+ },
+ "media": {
+ "allowInlinePlayback": true
+ },
+ "factSet": {
+ "title": {
+ "size": "Default",
+ "color": "Default",
+ "isSubtle": false,
+ "weight": "Bolder",
+ "wrap": true
+ },
+ "value": {
+ "size": "Default",
+ "color": "Default",
+ "isSubtle": false,
+ "weight": "Default",
+ "wrap": true
+ },
+ "spacing": 10
+ },
+ "cssClassNamePrefix": null
+}
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/values-v14/dimens.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/values-v14/dimens.xml
new file mode 100644
index 0000000000..4db8c59062
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/values-v14/dimens.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ 0dp
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/values/colors.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000000..73fa7c073c
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/values/colors.xml
@@ -0,0 +1,19 @@
+
+
+ #008577
+ #00574B
+ #D81B60
+
+
+ #272727
+
+
+ #242424
+ #edf0f2
+ #000000
+ #f4f4f4
+
+
+ #97FE46
+ #373736
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/values/dimens.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000000..22425a6da8
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/values/dimens.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ 8dp
+
+
+ 80dp
+ 50dp
+ 30dp
+ 10dp
+
+
+ 4dp
+
+
+ 8dp
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/values/strings.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..70b0a58601
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/values/strings.xml
@@ -0,0 +1,53 @@
+
+ Virtual Assistant MKII
+
+
+ Permission to record audio
+ In order for this app to listen to your voice, it requires permission use the microphone
+ You will only be able to type to the Bot
+ GPS Permission
+ Location permission is required to be able to direct you to points of interest
+
+
+ I am listening…
+ I am no longer listening…
+ Reconnecting to Service
+
+
+ Type a message to the bot
+ OK
+ Cancel
+
+
+ Open navigation drawer
+ Close navigation drawer
+ WebView UI
+ Send Location Event
+ Send Welcome Event
+ Disable Overlay
+ Show Textinput
+ Inject Adaptive Card Event
+ Reset Bot
+ Enable KWS
+ Bot Configuration
+ App Configuration
+ Show Assistant Settings
+
+
+ Service Key
+ Service Region
+ Bot ID
+ Voice Name
+ User ID
+ Locale
+ Lat
+ Lon
+ Chat history line-count
+
+
+ Talk
+
+
+ Listen
+
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/values/styles.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000000..662e5fc5f4
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/values/styles.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/xml/assistant_service.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/xml/assistant_service.xml
new file mode 100644
index 0000000000..193a33e330
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/xml/assistant_service.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/xml/widget_bot_request_info.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/xml/widget_bot_request_info.xml
new file mode 100644
index 0000000000..e065383f7a
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/xml/widget_bot_request_info.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/xml/widget_bot_response_info.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/xml/widget_bot_response_info.xml
new file mode 100644
index 0000000000..12403ddabb
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/xml/widget_bot_response_info.xml
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/app/src/main/res/xml/widget_talk_button_info.xml b/solutions/android/VirtualAssistantClient/app/src/main/res/xml/widget_talk_button_info.xml
new file mode 100644
index 0000000000..e2e3849020
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/app/src/main/res/xml/widget_talk_button_info.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/build.gradle b/solutions/android/VirtualAssistantClient/build.gradle
new file mode 100644
index 0000000000..6103339801
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/build.gradle
@@ -0,0 +1,28 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.4.1'
+ classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ maven { url 'https://maven.google.com' }
+ mavenCentral()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/.gitignore b/solutions/android/VirtualAssistantClient/directlinespeech/.gitignore
new file mode 100644
index 0000000000..796b96d1c4
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/build.gradle b/solutions/android/VirtualAssistantClient/directlinespeech/build.gradle
new file mode 100644
index 0000000000..0066de8048
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/build.gradle
@@ -0,0 +1,65 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 28
+
+ defaultConfig {
+ minSdkVersion 23
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ }
+
+ sourceSets {
+ main {
+ jniLibs.srcDirs 'libs'
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+// so that the library project can locate the aar in /libs
+repositories {
+ flatDir {
+ dirs 'libs'
+ }
+}
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation fileTree(include: ['*.aar'], dir: 'libs')
+ implementation fileTree(include: ['*.so'], dir: 'libs')
+ // implementation 'com.android.support:appcompat-v7:28.0.0'
+ // 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'
+
+ // for Client models
+ implementation "io.swagger:swagger-annotations:1.5.15"
+ implementation "org.threeten:threetenbp:1.3.5"
+ implementation "com.google.code.gson:gson:2.8.4"
+ implementation "javax.annotation:jsr250-api:1.0"
+ implementation "io.gsonfire:gson-fire:1.8.0"
+
+ // for Events
+ implementation 'org.greenrobot:eventbus:3.1.1'
+
+ // for rxJava
+ implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
+ implementation 'io.reactivex.rxjava2:rxjava:2.1.9'
+ implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
+}
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/libs/client-sdk-1.5.0-rc.1-debug.aar b/solutions/android/VirtualAssistantClient/directlinespeech/libs/client-sdk-1.5.0-rc.1-debug.aar
new file mode 100644
index 0000000000..c25faef86a
Binary files /dev/null and b/solutions/android/VirtualAssistantClient/directlinespeech/libs/client-sdk-1.5.0-rc.1-debug.aar differ
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/proguard-rules.pro b/solutions/android/VirtualAssistantClient/directlinespeech/proguard-rules.pro
new file mode 100644
index 0000000000..f1b424510d
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/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/solutions/android/VirtualAssistantClient/directlinespeech/src/main/AndroidManifest.xml b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..69ce67773d
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/AndroidManifest.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActionTypes.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActionTypes.java
new file mode 100644
index 0000000000..4f45ee20c7
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActionTypes.java
@@ -0,0 +1,88 @@
+/*
+ * Microsoft Bot Connector API - v3.0
+ * The Bot Connector REST API allows your bot to send and receive messages to channels configured in the [Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST and JSON over HTTPS. Client libraries for this REST API are available. See below for a list. Many bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The Bot State REST API allows a bot to store and retrieve state associated with users and conversations. Authentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is described in detail in the [Connector Authentication](/en-us/restapi/authentication) document. # Client Libraries for the Bot Connector REST API * [Bot Builder for C#](/en-us/csharp/builder/sdkreference/) * [Bot Builder for Node.js](/en-us/node/builder/overview/) * Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json) � 2016 Microsoft
+ *
+ * OpenAPI spec version: v3
+ * Contact: botframework@microsoft.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+
+package client.model;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+
+/**
+ * Defines action types for clickable buttons.
+ */
+@JsonAdapter(ActionTypes.Adapter.class)
+public enum ActionTypes {
+
+ OPENURL("openUrl"),
+
+ IMBACK("imBack"),
+
+ POSTBACK("postBack"),
+
+ PLAYAUDIO("playAudio"),
+
+ PLAYVIDEO("playVideo"),
+
+ SHOWIMAGE("showImage"),
+
+ DOWNLOADFILE("downloadFile"),
+
+ SIGNIN("signin"),
+
+ CALL("call"),
+
+ PAYMENT("payment"),
+
+ MESSAGEBACK("messageBack");
+
+ private String value;
+
+ ActionTypes(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+ public static ActionTypes fromValue(String text) {
+ for (ActionTypes b : ActionTypes.values()) {
+ if (String.valueOf(b.value).equals(text)) {
+ return b;
+ }
+ }
+ return null;
+ }
+
+ public static class Adapter extends TypeAdapter {
+ @Override
+ public void write(final JsonWriter jsonWriter, final ActionTypes enumeration) throws IOException {
+ jsonWriter.value(enumeration.getValue());
+ }
+
+ @Override
+ public ActionTypes read(final JsonReader jsonReader) throws IOException {
+ String value = jsonReader.nextString();
+ return ActionTypes.fromValue(String.valueOf(value));
+ }
+ }
+}
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/Activity.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/Activity.java
new file mode 100644
index 0000000000..dd7daf210f
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/Activity.java
@@ -0,0 +1,1056 @@
+/*
+ * Microsoft Bot Connector API - v3.0
+ * The Bot Connector REST API allows your bot to send and receive messages to channels configured in the [Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST and JSON over HTTPS. Client libraries for this REST API are available. See below for a list. Many bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The Bot State REST API allows a bot to store and retrieve state associated with users and conversations. Authentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is described in detail in the [Connector Authentication](/en-us/restapi/authentication) document. # Client Libraries for the Bot Connector REST API * [Bot Builder for C#](/en-us/csharp/builder/sdkreference/) * [Bot Builder for Node.js](/en-us/node/builder/overview/) * Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json) � 2016 Microsoft
+ *
+ * OpenAPI spec version: v3
+ * Contact: botframework@microsoft.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+
+package client.model;
+
+import com.google.gson.annotations.SerializedName;
+
+import org.threeten.bp.OffsetDateTime;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * An Activity is the basic communication type for the Bot Framework 3.0 protocol.
+ */
+@ApiModel(description = "An Activity is the basic communication type for the Bot Framework 3.0 protocol.")
+@javax.annotation.Generated(value = "io.swagger.codegen.languages.JavaClientCodegen", date = "2018-08-29T10:06:15.114-07:00")
+public class Activity {
+ @SerializedName("type")
+ private ActivityTypes type = null;
+
+ @SerializedName("id")
+ private String id = null;
+
+ @SerializedName("timestamp")
+ private OffsetDateTime timestamp = null;
+
+ @SerializedName("localTimestamp")
+ private OffsetDateTime localTimestamp = null;
+
+ @SerializedName("serviceUrl")
+ private String serviceUrl = null;
+
+ @SerializedName("channelId")
+ private String channelId = null;
+
+ @SerializedName("from")
+ private ChannelAccount from = null;
+
+ @SerializedName("conversation")
+ private ConversationAccount conversation = null;
+
+ @SerializedName("recipient")
+ private ChannelAccount recipient = null;
+
+ @SerializedName("textFormat")
+ private TextFormatTypes textFormat = null;
+
+ @SerializedName("attachmentLayout")
+ private AttachmentLayoutTypes attachmentLayout = null;
+
+ @SerializedName("membersAdded")
+ private List membersAdded = null;
+
+ @SerializedName("membersRemoved")
+ private List membersRemoved = null;
+
+ @SerializedName("reactionsAdded")
+ private List reactionsAdded = null;
+
+ @SerializedName("reactionsRemoved")
+ private List reactionsRemoved = null;
+
+ @SerializedName("topicName")
+ private String topicName = null;
+
+ @SerializedName("historyDisclosed")
+ private Boolean historyDisclosed = null;
+
+ @SerializedName("locale")
+ private String locale = null;
+
+ @SerializedName("text")
+ private String text = null;
+
+ @SerializedName("speak")
+ private String speak = null;
+
+ @SerializedName("inputHint")
+ private InputHints inputHint = null;
+
+ @SerializedName("summary")
+ private String summary = null;
+
+ @SerializedName("suggestedActions")
+ private SuggestedActions suggestedActions = null;
+
+ @SerializedName("attachments")
+ private List attachments = null;
+
+ @SerializedName("entities")
+ private List entities = null;
+
+ @SerializedName("channelData")
+ private Object channelData = null;
+
+ @SerializedName("action")
+ private String action = null;
+
+ @SerializedName("replyToId")
+ private String replyToId = null;
+
+ @SerializedName("label")
+ private String label = null;
+
+ @SerializedName("valueType")
+ private String valueType = null;
+
+ @SerializedName("value")
+ private Object value = null;
+
+ @SerializedName("name")
+ private String name = null;
+
+ @SerializedName("relatesTo")
+ private ConversationReference relatesTo = null;
+
+ @SerializedName("code")
+ private EndOfConversationCodes code = null;
+
+ @SerializedName("expiration")
+ private OffsetDateTime expiration = null;
+
+ @SerializedName("importance")
+ private ActivityImportance importance = null;
+
+ @SerializedName("deliveryMode")
+ private DeliveryModes deliveryMode = null;
+
+ @SerializedName("listenFor")
+ private List listenFor = null;
+
+ @SerializedName("textHighlights")
+ private List textHighlights = null;
+
+ @SerializedName("semanticAction")
+ private SemanticAction semanticAction = null;
+
+ public Activity type(ActivityTypes type) {
+ this.type = type;
+ return this;
+ }
+
+ /**
+ * Contains the activity type.
+ * @return type
+ **/
+ @ApiModelProperty(value = "Contains the activity type.")
+ public ActivityTypes getType() {
+ return type;
+ }
+
+ public void setType(ActivityTypes type) {
+ this.type = type;
+ }
+
+ public Activity id(String id) {
+ this.id = id;
+ return this;
+ }
+
+ /**
+ * Contains an ID that uniquely identifies the activity on the channel.
+ * @return id
+ **/
+ @ApiModelProperty(value = "Contains an ID that uniquely identifies the activity on the channel.")
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public Activity timestamp(OffsetDateTime timestamp) {
+ this.timestamp = timestamp;
+ return this;
+ }
+
+ /**
+ * Contains the date and time that the message was sent, in UTC, expressed in ISO-8601 format.
+ * @return timestamp
+ **/
+ @ApiModelProperty(value = "Contains the date and time that the message was sent, in UTC, expressed in ISO-8601 format.")
+ public OffsetDateTime getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(OffsetDateTime timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public Activity localTimestamp(OffsetDateTime localTimestamp) {
+ this.localTimestamp = localTimestamp;
+ return this;
+ }
+
+ /**
+ * Contains the date and time that the message was sent, in local time, expressed in ISO-8601 format. For example, 2016-09-23T13:07:49.4714686-07:00.
+ * @return localTimestamp
+ **/
+ @ApiModelProperty(value = "Contains the date and time that the message was sent, in local time, expressed in ISO-8601 format. For example, 2016-09-23T13:07:49.4714686-07:00.")
+ public OffsetDateTime getLocalTimestamp() {
+ return localTimestamp;
+ }
+
+ public void setLocalTimestamp(OffsetDateTime localTimestamp) {
+ this.localTimestamp = localTimestamp;
+ }
+
+ public Activity serviceUrl(String serviceUrl) {
+ this.serviceUrl = serviceUrl;
+ return this;
+ }
+
+ /**
+ * Contains the URL that specifies the channel's service endpoint. Set by the channel.
+ * @return serviceUrl
+ **/
+ @ApiModelProperty(value = "Contains the URL that specifies the channel's service endpoint. Set by the channel.")
+ public String getServiceUrl() {
+ return serviceUrl;
+ }
+
+ public void setServiceUrl(String serviceUrl) {
+ this.serviceUrl = serviceUrl;
+ }
+
+ public Activity channelId(String channelId) {
+ this.channelId = channelId;
+ return this;
+ }
+
+ /**
+ * Contains an ID that uniquely identifies the channel. Set by the channel.
+ * @return channelId
+ **/
+ @ApiModelProperty(value = "Contains an ID that uniquely identifies the channel. Set by the channel.")
+ public String getChannelId() {
+ return channelId;
+ }
+
+ public void setChannelId(String channelId) {
+ this.channelId = channelId;
+ }
+
+ public Activity from(ChannelAccount from) {
+ this.from = from;
+ return this;
+ }
+
+ /**
+ * Identifies the sender of the message.
+ * @return from
+ **/
+ @ApiModelProperty(value = "Identifies the sender of the message.")
+ public ChannelAccount getFrom() {
+ return from;
+ }
+
+ public void setFrom(ChannelAccount from) {
+ this.from = from;
+ }
+
+ public Activity conversation(ConversationAccount conversation) {
+ this.conversation = conversation;
+ return this;
+ }
+
+ /**
+ * Identifies the conversation to which the activity belongs.
+ * @return conversation
+ **/
+ @ApiModelProperty(value = "Identifies the conversation to which the activity belongs.")
+ public ConversationAccount getConversation() {
+ return conversation;
+ }
+
+ public void setConversation(ConversationAccount conversation) {
+ this.conversation = conversation;
+ }
+
+ public Activity recipient(ChannelAccount recipient) {
+ this.recipient = recipient;
+ return this;
+ }
+
+ /**
+ * Identifies the recipient of the message.
+ * @return recipient
+ **/
+ @ApiModelProperty(value = "Identifies the recipient of the message.")
+ public ChannelAccount getRecipient() {
+ return recipient;
+ }
+
+ public void setRecipient(ChannelAccount recipient) {
+ this.recipient = recipient;
+ }
+
+ public Activity textFormat(TextFormatTypes textFormat) {
+ this.textFormat = textFormat;
+ return this;
+ }
+
+ /**
+ * Format of text fields Default:markdown
+ * @return textFormat
+ **/
+ @ApiModelProperty(value = "Format of text fields Default:markdown")
+ public TextFormatTypes getTextFormat() {
+ return textFormat;
+ }
+
+ public void setTextFormat(TextFormatTypes textFormat) {
+ this.textFormat = textFormat;
+ }
+
+ public Activity attachmentLayout(AttachmentLayoutTypes attachmentLayout) {
+ this.attachmentLayout = attachmentLayout;
+ return this;
+ }
+
+ /**
+ * The layout hint for multiple attachments. Default: list.
+ * @return attachmentLayout
+ **/
+ @ApiModelProperty(value = "The layout hint for multiple attachments. Default: list.")
+ public AttachmentLayoutTypes getAttachmentLayout() {
+ return attachmentLayout;
+ }
+
+ public void setAttachmentLayout(AttachmentLayoutTypes attachmentLayout) {
+ this.attachmentLayout = attachmentLayout;
+ }
+
+ public Activity membersAdded(List membersAdded) {
+ this.membersAdded = membersAdded;
+ return this;
+ }
+
+ public Activity addMembersAddedItem(ChannelAccount membersAddedItem) {
+ if (this.membersAdded == null) {
+ this.membersAdded = new ArrayList();
+ }
+ this.membersAdded.add(membersAddedItem);
+ return this;
+ }
+
+ /**
+ * The collection of members added to the conversation.
+ * @return membersAdded
+ **/
+ @ApiModelProperty(value = "The collection of members added to the conversation.")
+ public List getMembersAdded() {
+ return membersAdded;
+ }
+
+ public void setMembersAdded(List membersAdded) {
+ this.membersAdded = membersAdded;
+ }
+
+ public Activity membersRemoved(List membersRemoved) {
+ this.membersRemoved = membersRemoved;
+ return this;
+ }
+
+ public Activity addMembersRemovedItem(ChannelAccount membersRemovedItem) {
+ if (this.membersRemoved == null) {
+ this.membersRemoved = new ArrayList();
+ }
+ this.membersRemoved.add(membersRemovedItem);
+ return this;
+ }
+
+ /**
+ * The collection of members removed from the conversation.
+ * @return membersRemoved
+ **/
+ @ApiModelProperty(value = "The collection of members removed from the conversation.")
+ public List getMembersRemoved() {
+ return membersRemoved;
+ }
+
+ public void setMembersRemoved(List membersRemoved) {
+ this.membersRemoved = membersRemoved;
+ }
+
+ public Activity reactionsAdded(List reactionsAdded) {
+ this.reactionsAdded = reactionsAdded;
+ return this;
+ }
+
+ public Activity addReactionsAddedItem(MessageReaction reactionsAddedItem) {
+ if (this.reactionsAdded == null) {
+ this.reactionsAdded = new ArrayList();
+ }
+ this.reactionsAdded.add(reactionsAddedItem);
+ return this;
+ }
+
+ /**
+ * The collection of reactions added to the conversation.
+ * @return reactionsAdded
+ **/
+ @ApiModelProperty(value = "The collection of reactions added to the conversation.")
+ public List getReactionsAdded() {
+ return reactionsAdded;
+ }
+
+ public void setReactionsAdded(List reactionsAdded) {
+ this.reactionsAdded = reactionsAdded;
+ }
+
+ public Activity reactionsRemoved(List reactionsRemoved) {
+ this.reactionsRemoved = reactionsRemoved;
+ return this;
+ }
+
+ public Activity addReactionsRemovedItem(MessageReaction reactionsRemovedItem) {
+ if (this.reactionsRemoved == null) {
+ this.reactionsRemoved = new ArrayList();
+ }
+ this.reactionsRemoved.add(reactionsRemovedItem);
+ return this;
+ }
+
+ /**
+ * The collection of reactions removed from the conversation.
+ * @return reactionsRemoved
+ **/
+ @ApiModelProperty(value = "The collection of reactions removed from the conversation.")
+ public List getReactionsRemoved() {
+ return reactionsRemoved;
+ }
+
+ public void setReactionsRemoved(List reactionsRemoved) {
+ this.reactionsRemoved = reactionsRemoved;
+ }
+
+ public Activity topicName(String topicName) {
+ this.topicName = topicName;
+ return this;
+ }
+
+ /**
+ * The updated topic name of the conversation.
+ * @return topicName
+ **/
+ @ApiModelProperty(value = "The updated topic name of the conversation.")
+ public String getTopicName() {
+ return topicName;
+ }
+
+ public void setTopicName(String topicName) {
+ this.topicName = topicName;
+ }
+
+ public Activity historyDisclosed(Boolean historyDisclosed) {
+ this.historyDisclosed = historyDisclosed;
+ return this;
+ }
+
+ /**
+ * Indicates whether the prior history of the channel is disclosed.
+ * @return historyDisclosed
+ **/
+ @ApiModelProperty(value = "Indicates whether the prior history of the channel is disclosed.")
+ public Boolean isHistoryDisclosed() {
+ return historyDisclosed;
+ }
+
+ public void setHistoryDisclosed(Boolean historyDisclosed) {
+ this.historyDisclosed = historyDisclosed;
+ }
+
+ public Activity locale(String locale) {
+ this.locale = locale;
+ return this;
+ }
+
+ /**
+ * A locale name for the contents of the text field. The locale name is a combination of an ISO 639 two- or three-letter culture code associated with a language and an ISO 3166 two-letter subculture code associated with a country or region. The locale name can also correspond to a valid BCP-47 language tag.
+ * @return locale
+ **/
+ @ApiModelProperty(value = "A locale name for the contents of the text field. The locale name is a combination of an ISO 639 two- or three-letter culture code associated with a language and an ISO 3166 two-letter subculture code associated with a country or region. The locale name can also correspond to a valid BCP-47 language tag.")
+ public String getLocale() {
+ return locale;
+ }
+
+ public void setLocale(String locale) {
+ this.locale = locale;
+ }
+
+ public Activity text(String text) {
+ this.text = text;
+ return this;
+ }
+
+ /**
+ * The text content of the message.
+ * @return text
+ **/
+ @ApiModelProperty(value = "The text content of the message.")
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public Activity speak(String speak) {
+ this.speak = speak;
+ return this;
+ }
+
+ /**
+ * The text to speak.
+ * @return speak
+ **/
+ @ApiModelProperty(value = "The text to speak.")
+ public String getSpeak() {
+ return speak;
+ }
+
+ public void setSpeak(String speak) {
+ this.speak = speak;
+ }
+
+ public Activity inputHint(InputHints inputHint) {
+ this.inputHint = inputHint;
+ return this;
+ }
+
+ /**
+ * Indicates whether your bot is accepting, expecting, or ignoring user input after the message is delivered to the client.
+ * @return inputHint
+ **/
+ @ApiModelProperty(value = "Indicates whether your bot is accepting, expecting, or ignoring user input after the message is delivered to the client.")
+ public InputHints getInputHint() {
+ return inputHint;
+ }
+
+ public void setInputHint(InputHints inputHint) {
+ this.inputHint = inputHint;
+ }
+
+ public Activity summary(String summary) {
+ this.summary = summary;
+ return this;
+ }
+
+ /**
+ * The text to display if the channel cannot render cards.
+ * @return summary
+ **/
+ @ApiModelProperty(value = "The text to display if the channel cannot render cards.")
+ public String getSummary() {
+ return summary;
+ }
+
+ public void setSummary(String summary) {
+ this.summary = summary;
+ }
+
+ public Activity suggestedActions(SuggestedActions suggestedActions) {
+ this.suggestedActions = suggestedActions;
+ return this;
+ }
+
+ /**
+ * The suggested actions for the activity.
+ * @return suggestedActions
+ **/
+ @ApiModelProperty(value = "The suggested actions for the activity.")
+ public SuggestedActions getSuggestedActions() {
+ return suggestedActions;
+ }
+
+ public void setSuggestedActions(SuggestedActions suggestedActions) {
+ this.suggestedActions = suggestedActions;
+ }
+
+ public Activity attachments(List attachments) {
+ this.attachments = attachments;
+ return this;
+ }
+
+ public Activity addAttachmentsItem(Attachment attachmentsItem) {
+ if (this.attachments == null) {
+ this.attachments = new ArrayList();
+ }
+ this.attachments.add(attachmentsItem);
+ return this;
+ }
+
+ /**
+ * Attachments
+ * @return attachments
+ **/
+ @ApiModelProperty(value = "Attachments")
+ public List getAttachments() {
+ return attachments;
+ }
+
+ public void setAttachments(List attachments) {
+ this.attachments = attachments;
+ }
+
+ public Activity entities(List entities) {
+ this.entities = entities;
+ return this;
+ }
+
+ public Activity addEntitiesItem(Entity entitiesItem) {
+ if (this.entities == null) {
+ this.entities = new ArrayList();
+ }
+ this.entities.add(entitiesItem);
+ return this;
+ }
+
+ /**
+ * Represents the entities that were mentioned in the message.
+ * @return entities
+ **/
+ @ApiModelProperty(value = "Represents the entities that were mentioned in the message.")
+ public List getEntities() {
+ return entities;
+ }
+
+ public void setEntities(List entities) {
+ this.entities = entities;
+ }
+
+ public Activity channelData(Object channelData) {
+ this.channelData = channelData;
+ return this;
+ }
+
+ /**
+ * Contains channel-specific content.
+ * @return channelData
+ **/
+ @ApiModelProperty(value = "Contains channel-specific content.")
+ public Object getChannelData() {
+ return channelData;
+ }
+
+ public void setChannelData(Object channelData) {
+ this.channelData = channelData;
+ }
+
+ public Activity action(String action) {
+ this.action = action;
+ return this;
+ }
+
+ /**
+ * Indicates whether the recipient of a contactRelationUpdate was added or removed from the sender's contact list.
+ * @return action
+ **/
+ @ApiModelProperty(value = "Indicates whether the recipient of a contactRelationUpdate was added or removed from the sender's contact list.")
+ public String getAction() {
+ return action;
+ }
+
+ public void setAction(String action) {
+ this.action = action;
+ }
+
+ public Activity replyToId(String replyToId) {
+ this.replyToId = replyToId;
+ return this;
+ }
+
+ /**
+ * Contains the ID of the message to which this message is a reply.
+ * @return replyToId
+ **/
+ @ApiModelProperty(value = "Contains the ID of the message to which this message is a reply.")
+ public String getReplyToId() {
+ return replyToId;
+ }
+
+ public void setReplyToId(String replyToId) {
+ this.replyToId = replyToId;
+ }
+
+ public Activity label(String label) {
+ this.label = label;
+ return this;
+ }
+
+ /**
+ * A descriptive label for the activity.
+ * @return label
+ **/
+ @ApiModelProperty(value = "A descriptive label for the activity.")
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public Activity valueType(String valueType) {
+ this.valueType = valueType;
+ return this;
+ }
+
+ /**
+ * The type of the activity's value object.
+ * @return valueType
+ **/
+ @ApiModelProperty(value = "The type of the activity's value object.")
+ public String getValueType() {
+ return valueType;
+ }
+
+ public void setValueType(String valueType) {
+ this.valueType = valueType;
+ }
+
+ public Activity value(Object value) {
+ this.value = value;
+ return this;
+ }
+
+ /**
+ * A value that is associated with the activity.
+ * @return value
+ **/
+ @ApiModelProperty(value = "A value that is associated with the activity.")
+ public Object getValue() {
+ return value;
+ }
+
+ public void setValue(Object value) {
+ this.value = value;
+ }
+
+ public Activity name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * The name of the operation associated with an invoke or event activity.
+ * @return name
+ **/
+ @ApiModelProperty(value = "The name of the operation associated with an invoke or event activity.")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Activity relatesTo(ConversationReference relatesTo) {
+ this.relatesTo = relatesTo;
+ return this;
+ }
+
+ /**
+ * A reference to another conversation or activity.
+ * @return relatesTo
+ **/
+ @ApiModelProperty(value = "A reference to another conversation or activity.")
+ public ConversationReference getRelatesTo() {
+ return relatesTo;
+ }
+
+ public void setRelatesTo(ConversationReference relatesTo) {
+ this.relatesTo = relatesTo;
+ }
+
+ public Activity code(EndOfConversationCodes code) {
+ this.code = code;
+ return this;
+ }
+
+ /**
+ * The a code for endOfConversation activities that indicates why the conversation ended.
+ * @return code
+ **/
+ @ApiModelProperty(value = "The a code for endOfConversation activities that indicates why the conversation ended.")
+ public EndOfConversationCodes getCode() {
+ return code;
+ }
+
+ public void setCode(EndOfConversationCodes code) {
+ this.code = code;
+ }
+
+ public Activity expiration(OffsetDateTime expiration) {
+ this.expiration = expiration;
+ return this;
+ }
+
+ /**
+ * The time at which the activity should be considered to be \"expired\" and should not be presented to the recipient.
+ * @return expiration
+ **/
+ @ApiModelProperty(value = "The time at which the activity should be considered to be \"expired\" and should not be presented to the recipient.")
+ public OffsetDateTime getExpiration() {
+ return expiration;
+ }
+
+ public void setExpiration(OffsetDateTime expiration) {
+ this.expiration = expiration;
+ }
+
+ public Activity importance(ActivityImportance importance) {
+ this.importance = importance;
+ return this;
+ }
+
+ /**
+ * The importance of the activity.
+ * @return importance
+ **/
+ @ApiModelProperty(value = "The importance of the activity.")
+ public ActivityImportance getImportance() {
+ return importance;
+ }
+
+ public void setImportance(ActivityImportance importance) {
+ this.importance = importance;
+ }
+
+ public Activity deliveryMode(DeliveryModes deliveryMode) {
+ this.deliveryMode = deliveryMode;
+ return this;
+ }
+
+ /**
+ * A delivery hint to signal to the recipient alternate delivery paths for the activity. The default delivery mode is \"default\".
+ * @return deliveryMode
+ **/
+ @ApiModelProperty(value = "A delivery hint to signal to the recipient alternate delivery paths for the activity. The default delivery mode is \"default\".")
+ public DeliveryModes getDeliveryMode() {
+ return deliveryMode;
+ }
+
+ public void setDeliveryMode(DeliveryModes deliveryMode) {
+ this.deliveryMode = deliveryMode;
+ }
+
+ public Activity listenFor(List listenFor) {
+ this.listenFor = listenFor;
+ return this;
+ }
+
+ public Activity addListenForItem(String listenForItem) {
+ if (this.listenFor == null) {
+ this.listenFor = new ArrayList();
+ }
+ this.listenFor.add(listenForItem);
+ return this;
+ }
+
+ /**
+ * List of phrases and references that speech and language priming systems should listen for
+ * @return listenFor
+ **/
+ @ApiModelProperty(value = "List of phrases and references that speech and language priming systems should listen for")
+ public List getListenFor() {
+ return listenFor;
+ }
+
+ public void setListenFor(List listenFor) {
+ this.listenFor = listenFor;
+ }
+
+ public Activity textHighlights(List textHighlights) {
+ this.textHighlights = textHighlights;
+ return this;
+ }
+
+ public Activity addTextHighlightsItem(TextHighlight textHighlightsItem) {
+ if (this.textHighlights == null) {
+ this.textHighlights = new ArrayList();
+ }
+ this.textHighlights.add(textHighlightsItem);
+ return this;
+ }
+
+ /**
+ * The collection of text fragments to highlight when the activity contains a ReplyToId value.
+ * @return textHighlights
+ **/
+ @ApiModelProperty(value = "The collection of text fragments to highlight when the activity contains a ReplyToId value.")
+ public List getTextHighlights() {
+ return textHighlights;
+ }
+
+ public void setTextHighlights(List textHighlights) {
+ this.textHighlights = textHighlights;
+ }
+
+ public Activity semanticAction(SemanticAction semanticAction) {
+ this.semanticAction = semanticAction;
+ return this;
+ }
+
+ /**
+ * An optional programmatic action accompanying this request
+ * @return semanticAction
+ **/
+ @ApiModelProperty(value = "An optional programmatic action accompanying this request")
+ public SemanticAction getSemanticAction() {
+ return semanticAction;
+ }
+
+ public void setSemanticAction(SemanticAction semanticAction) {
+ this.semanticAction = semanticAction;
+ }
+
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Activity activity = (Activity) o;
+ return Objects.equals(this.type, activity.type) &&
+ Objects.equals(this.id, activity.id) &&
+ Objects.equals(this.timestamp, activity.timestamp) &&
+ Objects.equals(this.localTimestamp, activity.localTimestamp) &&
+ Objects.equals(this.serviceUrl, activity.serviceUrl) &&
+ Objects.equals(this.channelId, activity.channelId) &&
+ Objects.equals(this.from, activity.from) &&
+ Objects.equals(this.conversation, activity.conversation) &&
+ Objects.equals(this.recipient, activity.recipient) &&
+ Objects.equals(this.textFormat, activity.textFormat) &&
+ Objects.equals(this.attachmentLayout, activity.attachmentLayout) &&
+ Objects.equals(this.membersAdded, activity.membersAdded) &&
+ Objects.equals(this.membersRemoved, activity.membersRemoved) &&
+ Objects.equals(this.reactionsAdded, activity.reactionsAdded) &&
+ Objects.equals(this.reactionsRemoved, activity.reactionsRemoved) &&
+ Objects.equals(this.topicName, activity.topicName) &&
+ Objects.equals(this.historyDisclosed, activity.historyDisclosed) &&
+ Objects.equals(this.locale, activity.locale) &&
+ Objects.equals(this.text, activity.text) &&
+ Objects.equals(this.speak, activity.speak) &&
+ Objects.equals(this.inputHint, activity.inputHint) &&
+ Objects.equals(this.summary, activity.summary) &&
+ Objects.equals(this.suggestedActions, activity.suggestedActions) &&
+ Objects.equals(this.attachments, activity.attachments) &&
+ Objects.equals(this.entities, activity.entities) &&
+ Objects.equals(this.channelData, activity.channelData) &&
+ Objects.equals(this.action, activity.action) &&
+ Objects.equals(this.replyToId, activity.replyToId) &&
+ Objects.equals(this.label, activity.label) &&
+ Objects.equals(this.valueType, activity.valueType) &&
+ Objects.equals(this.value, activity.value) &&
+ Objects.equals(this.name, activity.name) &&
+ Objects.equals(this.relatesTo, activity.relatesTo) &&
+ Objects.equals(this.code, activity.code) &&
+ Objects.equals(this.expiration, activity.expiration) &&
+ Objects.equals(this.importance, activity.importance) &&
+ Objects.equals(this.deliveryMode, activity.deliveryMode) &&
+ Objects.equals(this.listenFor, activity.listenFor) &&
+ Objects.equals(this.textHighlights, activity.textHighlights) &&
+ Objects.equals(this.semanticAction, activity.semanticAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, id, timestamp, localTimestamp, serviceUrl, channelId, from, conversation, recipient, textFormat, attachmentLayout, membersAdded, membersRemoved, reactionsAdded, reactionsRemoved, topicName, historyDisclosed, locale, text, speak, inputHint, summary, suggestedActions, attachments, entities, channelData, action, replyToId, label, valueType, value, name, relatesTo, code, expiration, importance, deliveryMode, listenFor, textHighlights, semanticAction);
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class Activity {\n");
+
+ sb.append(" type: ").append(toIndentedString(type)).append("\n");
+ sb.append(" id: ").append(toIndentedString(id)).append("\n");
+ sb.append(" timestamp: ").append(toIndentedString(timestamp)).append("\n");
+ sb.append(" localTimestamp: ").append(toIndentedString(localTimestamp)).append("\n");
+ sb.append(" serviceUrl: ").append(toIndentedString(serviceUrl)).append("\n");
+ sb.append(" channelId: ").append(toIndentedString(channelId)).append("\n");
+ sb.append(" from: ").append(toIndentedString(from)).append("\n");
+ sb.append(" conversation: ").append(toIndentedString(conversation)).append("\n");
+ sb.append(" recipient: ").append(toIndentedString(recipient)).append("\n");
+ sb.append(" textFormat: ").append(toIndentedString(textFormat)).append("\n");
+ sb.append(" attachmentLayout: ").append(toIndentedString(attachmentLayout)).append("\n");
+ sb.append(" membersAdded: ").append(toIndentedString(membersAdded)).append("\n");
+ sb.append(" membersRemoved: ").append(toIndentedString(membersRemoved)).append("\n");
+ sb.append(" reactionsAdded: ").append(toIndentedString(reactionsAdded)).append("\n");
+ sb.append(" reactionsRemoved: ").append(toIndentedString(reactionsRemoved)).append("\n");
+ sb.append(" topicName: ").append(toIndentedString(topicName)).append("\n");
+ sb.append(" historyDisclosed: ").append(toIndentedString(historyDisclosed)).append("\n");
+ sb.append(" locale: ").append(toIndentedString(locale)).append("\n");
+ sb.append(" text: ").append(toIndentedString(text)).append("\n");
+ sb.append(" speak: ").append(toIndentedString(speak)).append("\n");
+ sb.append(" inputHint: ").append(toIndentedString(inputHint)).append("\n");
+ sb.append(" summary: ").append(toIndentedString(summary)).append("\n");
+ sb.append(" suggestedActions: ").append(toIndentedString(suggestedActions)).append("\n");
+ sb.append(" attachments: ").append(toIndentedString(attachments)).append("\n");
+ sb.append(" entities: ").append(toIndentedString(entities)).append("\n");
+ sb.append(" channelData: ").append(toIndentedString(channelData)).append("\n");
+ sb.append(" action: ").append(toIndentedString(action)).append("\n");
+ sb.append(" replyToId: ").append(toIndentedString(replyToId)).append("\n");
+ sb.append(" label: ").append(toIndentedString(label)).append("\n");
+ sb.append(" valueType: ").append(toIndentedString(valueType)).append("\n");
+ sb.append(" value: ").append(toIndentedString(value)).append("\n");
+ sb.append(" name: ").append(toIndentedString(name)).append("\n");
+ sb.append(" relatesTo: ").append(toIndentedString(relatesTo)).append("\n");
+ sb.append(" code: ").append(toIndentedString(code)).append("\n");
+ sb.append(" expiration: ").append(toIndentedString(expiration)).append("\n");
+ sb.append(" importance: ").append(toIndentedString(importance)).append("\n");
+ sb.append(" deliveryMode: ").append(toIndentedString(deliveryMode)).append("\n");
+ sb.append(" listenFor: ").append(toIndentedString(listenFor)).append("\n");
+ sb.append(" textHighlights: ").append(toIndentedString(textHighlights)).append("\n");
+ sb.append(" semanticAction: ").append(toIndentedString(semanticAction)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+}
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActivityImportance.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActivityImportance.java
new file mode 100644
index 0000000000..17d6c9bcb6
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActivityImportance.java
@@ -0,0 +1,72 @@
+/*
+ * Microsoft Bot Connector API - v3.0
+ * The Bot Connector REST API allows your bot to send and receive messages to channels configured in the [Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST and JSON over HTTPS. Client libraries for this REST API are available. See below for a list. Many bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The Bot State REST API allows a bot to store and retrieve state associated with users and conversations. Authentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is described in detail in the [Connector Authentication](/en-us/restapi/authentication) document. # Client Libraries for the Bot Connector REST API * [Bot Builder for C#](/en-us/csharp/builder/sdkreference/) * [Bot Builder for Node.js](/en-us/node/builder/overview/) * Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json) � 2016 Microsoft
+ *
+ * OpenAPI spec version: v3
+ * Contact: botframework@microsoft.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+
+package client.model;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+
+/**
+ * Defines the importance of an Activity
+ */
+@JsonAdapter(ActivityImportance.Adapter.class)
+public enum ActivityImportance {
+
+ LOW("low"),
+
+ NORMAL("normal"),
+
+ HIGH("high");
+
+ private String value;
+
+ ActivityImportance(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+ public static ActivityImportance fromValue(String text) {
+ for (ActivityImportance b : ActivityImportance.values()) {
+ if (String.valueOf(b.value).equals(text)) {
+ return b;
+ }
+ }
+ return null;
+ }
+
+ public static class Adapter extends TypeAdapter {
+ @Override
+ public void write(final JsonWriter jsonWriter, final ActivityImportance enumeration) throws IOException {
+ jsonWriter.value(enumeration.getValue());
+ }
+
+ @Override
+ public ActivityImportance read(final JsonReader jsonReader) throws IOException {
+ String value = jsonReader.nextString();
+ return ActivityImportance.fromValue(String.valueOf(value));
+ }
+ }
+}
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActivitySet.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActivitySet.java
new file mode 100644
index 0000000000..953d8b256f
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActivitySet.java
@@ -0,0 +1,124 @@
+/*
+ * Bot Connector - Direct Line API - v3.0
+ * Direct Line 3.0 =============== The Direct Line API is a simple REST API for connecting directly to a single bot. This API is intended for developers writing their own client applications, web chat controls, mobile apps, or service-to-service applications that will talk to their bot. Within the Direct Line API, you will find: * An **authentication mechanism** using standard secret/token patterns * The ability to **send** messages from your client to your bot via an HTTP POST message * The ability to **receive** messages by **WebSocket** stream, if you choose * The ability to **receive** messages by **polling HTTP GET**, if you choose * A stable **schema**, even if your bot changes its protocol version Direct Line 1.1 and 3.0 are both available and supported. This document describes Direct Line 3.0. For information on Direct Line 1.1, visit the [Direct Line 1.1 reference documentation](/en-us/restapi/directline/). # Authentication: Secrets and Tokens Direct Line allows you to authenticate all calls with either a secret (retrieved from the Direct Line channel configuration page) or a token (which you may get at runtime by converting your secret). A Direct Line **secret** is a master key that can access any conversation, and create tokens. Secrets do not expire. A Direct Line **token** is a key for a single conversation. It expires but can be refreshed. If you're writing a service-to-service application, using the secret may be simplest. If you're writing an application where the client runs in a web browser or mobile app, you may want to exchange your secret for a token, which only works for a single conversation and will expire unless refreshed. You choose which security model works best for you. Your secret or token is communicated in the ```Authorization``` header of every call, with the Bearer scheme. Example below. ``` -- connect to directline.botframework.com -- POST /v3/directline/conversations/abc123/activities HTTP/1.1 Authorization: Bearer RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0 [other HTTP headers, omitted] ``` You may notice that your Direct Line client credentials are different from your bot's credentials. This is intentional, and it allows you to revise your keys independently and lets you share client tokens without disclosing your bot's password. ## Exchanging a secret for a token This operation is optional. Use this step if you want to prevent clients from accessing conversations they aren't participating in. To exchange a secret for a token, POST to /v3/directline/tokens/generate with your secret in the auth header and no HTTP body. ``` -- connect to directline.botframework.com -- POST /v3/directline/tokens/generate HTTP/1.1 Authorization: Bearer RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0 [other headers] -- response from directline.botframework.com -- HTTP/1.1 200 OK [other headers] { \"conversationId\": \"abc123\", \"token\": \"RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0y8qbOF5xPGfiCpg4Fv0y8qqbOF5x8qbOF5xn\", \"expires_in\": 1800 } ``` If successful, the response is a token suitable for one conversation. The token expires in the seconds indicated in the ```expires_in``` field (30 minutes in the example above) and must be refreshed before then to remain useful. This call is similar to ```/v3/directline/conversations```. The difference is that the call to ```/v3/directline/tokens/generate``` does not start the conversation, does not contact the bot, and does not create a streaming WebSocket URL. * Call ```/v3/directline/conversations``` if you will distribute the token to client(s) and want them to initiate the conversation. * Call ```/v3/directline/conversations``` if you intend to start the conversation immediately. ## Refreshing a token A token may be refreshed an unlimited number of times unless it is expired. To refresh a token, POST to /v3/directline/tokens/refresh. This method is valid only for unexpired tokens. ``` -- connect to directline.botframework.com -- POST /v3/directline/tokens/refresh HTTP/1.1 Authorization: Bearer RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0y8qbOF5xPGfiCpg4Fv0y8qqbOF5x8qbOF5xn [other headers] -- response from directline.botframework.com -- HTTP/1.1 200 OK [other headers] { \"conversationId\": \"abc123\", \"token\": \"RCurR_XV9ZA.cwA.BKA.y8qbOF5xPGfiCpg4Fv0y8qqbOF5x8qbOF5xniaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0\", \"expires_in\": 1800 } ``` # REST calls for a Direct Line conversation Direct Line conversations are explicitly opened by clients and may run as long as the bot and client participate (and have valid credentials). While the conversation is open, the bot and client may both send messages. More than one client may connect to a given conversation and each client may participate on behalf of multiple users. ## Starting a conversation Clients begin by explicitly starting a conversation. If successful, the Direct Line service replies with a JSON object containing a conversation ID, a token, and a WebSocket URL that may be used later. ``` -- connect to directline.botframework.com -- POST /v3/directline/conversations HTTP/1.1 Authorization: Bearer RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0y8qbOF5xPGfiCpg4Fv0y8qqbOF5x8qbOF5xn [other headers] -- response from directline.botframework.com -- HTTP/1.1 201 Created [other headers] { \"conversationId\": \"abc123\", \"token\": \"RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0y8qbOF5xPGfiCpg4Fv0y8qqbOF5x8qbOF5xn\", \"expires_in\": 1800, \"streamUrl\": \"https://directline.botframework.com/v3/directline/conversations/abc123/stream?t=RCurR_XV9ZA.cwA...\" } ``` If the conversation was started, an HTTP 201 status code is returned. HTTP 201 is the code that clients will receive under most circumstances, as the typical use case is for a client to start a new conversation. Under certain conditions -- specifically, when the client has a token scoped to a single conversation AND when that conversation was started with a prior call to this URL -- this method will return HTTP 200 to signify the request was acceptable but that no conversation was created (as it already existed). You have 60 seconds to connect to the WebSocket URL. If the connection cannot be established during this time, use the reconnect method below to generate a new stream URL. This call is similar to ```/v3/directline/tokens/generate```. The difference is that the call to ```/v3/directline/conversations``` starts the conversation, contacts the bot, and creates a streaming WebSocket URL, none of which occur when generating a token. * Call ```/v3/directline/conversations``` if you will distribute the token to client(s) and want them to initiate the conversation. * Call ```/v3/directline/conversations``` if you intend to start the conversation immediately. ## Reconnecting to a conversation If a client is using the WebSocket interface to receive messages but loses its connection, it may need to reconnect. Reconnecting requires generating a new WebSocket stream URL, and this can be accomplished by sending a GET request to the ```/v3/directline/conversations/{id}``` endpoint. The ```watermark``` parameter is optional. If supplied, the conversation replays from the watermark, guaranteeing no messages are lost. If ```watermark``` is omitted, only messages received after the reconnection call (```GET /v3/directline/conversations/abc123```) are replayed. ``` -- connect to directline.botframework.com -- GET /v3/directline/conversations/abc123?watermark=0000a-42 HTTP/1.1 Authorization: Bearer RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0y8qbOF5xPGfiCpg4Fv0y8qqbOF5x8qbOF5xn [other headers] -- response from directline.botframework.com -- HTTP/1.1 200 OK [other headers] { \"conversationId\": \"abc123\", \"token\": \"RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0y8qbOF5xPGfiCpg4Fv0y8qqbOF5x8qbOF5xn\", \"streamUrl\": \"https://directline.botframework.com/v3/directline/conversations/abc123/stream?watermark=000a-4&t=RCurR_XV9ZA.cwA...\" } ``` You have 60 seconds to connect to the WebSocket stream URL. If the connection cannot be established during this time, issue another reconnect request to get an updated stream URL. ## Sending an Activity to the bot Using the Direct Line 3.0 protocol, clients and bots may exchange many different Bot Framework v3 Activites, including Message Activities, Typing Activities, and custom activities that the bot supports. To send any one of these activities to the bot, 1. the client formulates the Activity according to the Activity schema (see below) 2. the client issues a POST message to ```/v3/directline/conversations/{id}/activities``` 3. the service returns when the activity was delivered to the bot, with an HTTP status code reflecting the bot's status code. If the POST was successful, the service returns a JSON payload containing the ID of the Activity that was sent. Example follows. ``` -- connect to directline.botframework.com -- POST /v3/directline/conversations/abc123/activities HTTP/1.1 Authorization: Bearer RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0 [other headers] { \"type\": \"message\", \"from\": { \"id\": \"user1\" }, \"text\": \"hello\" } -- response from directline.botframework.com -- HTTP/1.1 200 OK [other headers] { \"id\": \"0001\" } ``` The client's Activity is available in the message retrieval path (either polling GET or WebSocket) and is not returned inline. The total time to POST a message to a Direct Line conversation is: * Transit time to the Direct Line service, * Internal processing time within Direct Line (typically less than 120ms) * Transit time to the bot * Processing time within the bot * Transit time for HTTP responses to travel back to the client. If the bot generates an error, that error will trigger an HTTP 502 error (\"Bad Gateway\") in the ```POST /v3/directline/conversations/{id}/activities``` call. ### Sending one or more attachments by URL Clients may optionally send attachments, such as images or documents. If the client already has a URL for the attachment, the simplest way to send it is to include the URL in the ```contentUrl``` field of an Activity attachment object. This applies to HTTP, HTTPS, and ```data:``` URIs. ### Sending a single attachment by upload Often, clients have an image or document on a device but no URL that can be included in the activity. To upload an attachment, POST a single attachment to the ```/v3/directline/conversations/{conversationId}/upload``` endpoint. The ```Content-Type``` and ```Content-Disposition``` headers control the attachment's type and filename, respectively. A user ID is required. Supply the ID of the user sending the attachment as a ```userId``` parameter in the URL. If uploading a single attachment, a message activity is sent to the bot when the upload completes. On completion, the service returns the ID of the activity that was sent. ``` -- connect to directline.botframework.com -- POST /v3/directline/conversations/abc123/upload?userId=user1 HTTP/1.1 Authorization: Bearer RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0 Content-Type: image/jpeg Content-Disposition: name=\"file\"; filename=\"badjokeeel.jpg\" [other headers] [JPEG content] -- response from directline.botframework.com -- HTTP/1.1 200 OK [other headers] { \"id\": \"0003\" } ``` ### Sending multiple attachments by upload If uploading multiple attachments, use ```multipart/form-data``` as the content type and include each attachment as a separate part. Each attachment's type and filename may be included in the ```Content-Type``` and ```Content-Disposition``` headers in each part. An activity may be included by adding a part with content type of ```application/vnd.microsoft.activity```. Other parts in the payload are attached to this activity before it is sent. If an Activity is not included, an empty Activity is created as a wrapper for the attachments. ``` -- connect to directline.botframework.com -- POST /v3/directline/conversations/abc123/upload?userId=user1 HTTP/1.1 Authorization: Bearer RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0 Content-Type: multipart/form-data; boundary=----DD4E5147-E865-4652-B662-F223701A8A89 [other headers] ----DD4E5147-E865-4652-B662-F223701A8A89 Content-Type: image/jpeg Content-Disposition: form-data; name=\"file\"; filename=\"badjokeeel.jpg\" [other headers] [JPEG content] ----DD4E5147-E865-4652-B662-F223701A8A89 Content-Type: application/vnd.microsoft.activity [other headers] { \"type\": \"message\", \"from\": { \"id\": \"user1\" }, \"text\": \"Hey I just IM'd you\\n\\nand this is crazy\\n\\nbut here's my webhook\\n\\nso POST me maybe\" } ----DD4E5147-E865-4652-B662-F223701A8A89 -- response from directline.botframework.com -- HTTP/1.1 200 OK [other headers] { \"id\": \"0004\" } ``` ## Receiving Activities from the bot Direct Line 3.0 clients may choose from two different mechanisms for retrieving messages: 1. A **streaming WebSocket**, which pushes messages efficiently to clients. 2. A **polling GET** interface, which is available for clients unable to use WebSockets or for clients retrieving the conversation history. **Not all activities are available via the polling GET interface.** A table of activity availability follows. |Activity type|Availability| |-------------|--------| |Message|Polling GET and WebSocket| |Typing|WebSocket only| |ConversationUpdate|Not sent/received via client| |ContactRelationUpdate|Not supported in Direct Line| |EndOfConversation|Polling GET and WebSocket| |All other activity types|Polling GET and WebSocket| ### Receiving Activities by WebSocket To connect via WebSocket, a client uses the StreamUrl when starting a conversation. The stream URL is preauthorized and does NOT require an Authorization header containing the client's secret or token. ``` -- connect to wss://directline.botframework.com -- GET /v3/directline/conversations/abc123/stream?t=RCurR_XV9ZA.cwA...\" HTTP/1.1 Upgrade: websocket Connection: upgrade [other headers] -- response from directline.botframework.com -- HTTP/1.1 101 Switching Protocols [other headers] ``` The Direct Line service sends the following messages: * An **ActivitySet**, which contains one or more activities and a watermark (described below) * An empty message, which the Direct Line service uses to ensure the connection is still valid * Additional types, to be defined later. These types are identified by the properties in the JSON root. ActivitySets contain messages sent by the bot and by all users. Example ActivitySet: ``` { \"activities\": [{ \"type\": \"message\", \"channelId\": \"directline\", \"conversation\": { \"id\": \"abc123\" }, \"id\": \"abc123|0000\", \"from\": { \"id\": \"user1\" }, \"text\": \"hello\" }], \"watermark\": \"0000a-42\" } ``` Clients should keep track of the \"watermark\" value from each ActivitySet so they can use it on reconnect. **Note** that a ```null``` or missing watermark should be ignored and should not overwrite a prior watermark in the client. Clients should ignore empty messages. Clients may send their own empty messages to verify connectivity. The Direct Line service will ignore these. The service may forcibly close the connection under certain conditions. If the client has not received an EndOfConversation activity, it may reconnect by issuing a GET request to the conversation endpoint to get a new stream URL (see above). The WebSocket stream contains live updates and very recent messages (since the call to get the WebSocket call was issued) but it does not include messages sent prior to the most recent POST to ```/v3/directline/conversations/{id}```. To retrieve messages sent earlier in the conversation, use the GET mechanism below. ### Receiving Activities by GET The GET mechanism is useful for clients who are unable to use the WebSocket, or for clients wishing to retrieve the conversation history. To retrieve messages, issue a GET call to the conversation endpoint. Optionally supply a watermark, indicating the most recent message seen. The watermark field accompanies all GET/WebSocket messages as a property in the ActivitySet. ``` -- connect to directline.botframework.com -- GET /v3/directline/conversations/abc123/activities?watermark=0001a-94 HTTP/1.1 Authorization: Bearer RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0 [other headers] -- response from directline.botframework.com -- HTTP/1.1 200 OK [other headers] { \"activities\": [{ \"type\": \"message\", \"channelId\": \"directline\", \"conversation\": { \"id\": \"abc123\" }, \"id\": \"abc123|0000\", \"from\": { \"id\": \"user1\" }, \"text\": \"hello\" }, { \"type\": \"message\", \"channelId\": \"directline\", \"conversation\": { \"id\": \"abc123\" }, \"id\": \"abc123|0001\", \"from\": { \"id\": \"bot1\" }, \"text\": \"Nice to see you, user1!\" }], \"watermark\": \"0001a-95\" } ``` Clients should page through the available activities by advancing the ```watermark``` value until no activities are returned. ### Timing considerations Most clients wish to retain a complete message history. Even though Direct Line is a multi-part protocol with potential timing gaps, the protocol and service is designed to make it easy to build a reliable client. 1. The ```watermark``` field sent in the WebSocket stream and GET response is reliable. You will not miss messages as long as you replay the watermark verbatim. 2. When starting a conversation and connecting to the WebSocket stream, any Activities sent after the POST but before the socket is opened are replayed before new messages. 3. When refreshing history by GET call while connected to the WebSocket, Activities may be duplicated across both channels. Keeping a list of all known Activity IDs will allow you to reject duplicate messages should they occur. Clients using the polling GET interface should choose a polling interval that matches their intended use. * Service-to-service applications often use a polling interval of 5s or 10s. * Client-facing applications often use a polling interval of 1s, and fire an additional request ~300ms after every message the client sends to rapidly pick up a bot's response. This 300ms delay should be adjusted based on the bot's speed and transit time. ## Ending a conversation Either a client or a bot may signal the end of a DirectLine conversation. This operation halts communication and prevents the bot and the client from sending messages. Messages may still be retrieved via the GET mechanism. Sending this messages is as simple as POSTing an EndOfConversation activity. ``` -- connect to directline.botframework.com -- POST /v3/directline/conversations/abc123/activities HTTP/1.1 Authorization: Bearer RCurR_XV9ZA.cwA.BKA.iaJrC8xpy8qbOF5xnR2vtCX7CZj0LdjAPGfiCpg4Fv0 [other headers] { \"type\": \"endOfConversation\", \"from\": { \"id\": \"user1\" } } -- response from directline.botframework.com -- HTTP/1.1 200 OK [other headers] { \"id\": \"0004\" } ``` ## REST API errors HTTP calls to the Direct Line service follow standard HTTP error conventions: * 2xx status codes indicate success. (Direct Line 3.0 uses 200 and 201.) * 4xx status codes indicate an error in your request. * 401 indicates a missing or malformed Authorization header (or URL token, in calls where a token parameter is allowed). * 403 indicates an unauthorized client. * If calling with a valid but expired token, the ```code``` field is set to ```TokenExpired```. * 404 indicates a missing path, site, conversation, etc. * 5xx status codes indicate a service-side error. * 500 indicates an error inside the Direct Line service. * 502 indicates an error was returned by the bot. **This is a common error code.** * 101 is used in the WebSocket connection path, although this is likely handled by your WebSocket client. When an error message is returned, error detail may be present in a JSON response. Look for an ```error``` property with ```code``` and ```message``` fields. ``` -- connect to directline.botframework.com -- POST /v3/directline/conversations/abc123/activities HTTP/1.1 [detail omitted] -- response from directline.botframework.com -- HTTP/1.1 502 Bad Gateway [other headers] { \"error\": { \"code\": \"BotRejectedActivity\", \"message\": \"Failed to send activity: bot returned an error\" } } ``` The contents of the ```message``` field may change. The HTTP status code and values in the ```code``` property are stable. # Schema The Direct Line 3.0 schema is identical to the Bot Framework v3 schema. When a bot sends an Activity to a client through Direct Line: * attachment cards are preserved, * URLs for uploaded attachments are hidden with a private link, and * the ```channelData``` property is preserved without modification. When a client sends an Activity to a bot through Direct Line: * the ```type``` property contains the kind of activity you are sending (typically ```message```), * the ```from``` property must be populated with a user ID, chosen by your client, * attachments may contain URLs to existing resources or URLs uploaded through the Direct Line attachment endpoint, and * the ```channelData``` property is preserved without modification. Clients and bots may send Activities of any type, including Message Activities, Typing Activities, and custom Activity types. Clients may send a single Activity at a time. ``` { \"type\": \"message\", \"channelId\": \"directline\", \"from\": { \"id\": \"user1\" }, \"text\": \"hello\" } ``` Clients receive multiple Activities as part of an ActivitySet. The ActivitySet has an array of activities and a watermark field. ``` { \"activities\": [{ \"type\": \"message\", \"channelId\": \"directline\", \"conversation\": { \"id\": \"abc123\" }, \"id\": \"abc123|0000\", \"from\": { \"id\": \"user1\" }, \"text\": \"hello\" }], \"watermark\": \"0000a-42\" } ``` # Libraries for the Direct Line API The Direct Line API is designed to be coded directly, but the Bot Framework includes libraries and controls that help you to embed Direct-Line-powered bots into your application. * The [Bot Framework Web Chat control](https://github.com/Microsoft/BotFramework-WebChat) is an easy way to embed the Direct Line protocol into a webpage. * [Direct Line Nuget package](https://www.nuget.org/packages/Microsoft.Bot.Connector.DirectLine) with libraries for .Net 4.5, UWP, and .Net Standard. * [DirectLineJs](https://github.com/Microsoft/BotFramework-DirectLineJs), also available on [NPM](https://www.npmjs.com/package/botframework-directlinejs) * You may generate your own from the [Direct Line Swagger file](swagger.json) Our [BotBuilder-Samples GitHub repo](https://github.com/Microsoft/BotBuilder-Samples) also contains samples for [C#](https://github.com/Microsoft/BotBuilder-Samples/tree/master/CSharp/core-DirectLine) and [JavaScript](https://github.com/Microsoft/BotBuilder-Samples/tree/master/Node/core-DirectLine).
+ *
+ * OpenAPI spec version: v3
+ * Contact: botframework@microsoft.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+
+package client.model;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * A collection of activities
+ */
+@ApiModel(description = "A collection of activities")
+@javax.annotation.Generated(value = "io.swagger.codegen.languages.JavaClientCodegen", date = "2018-08-28T10:47:05.767-07:00")
+public class ActivitySet {
+ @SerializedName("activities")
+ private List activities = null;
+
+ @SerializedName("watermark")
+ private String watermark = null;
+
+ public ActivitySet activities(List activities) {
+ this.activities = activities;
+ return this;
+ }
+
+ public ActivitySet addActivitiesItem(Activity activitiesItem) {
+ if (this.activities == null) {
+ this.activities = new ArrayList();
+ }
+ this.activities.add(activitiesItem);
+ return this;
+ }
+
+ /**
+ * Activities
+ * @return activities
+ **/
+ @ApiModelProperty(value = "Activities")
+ public List getActivities() {
+ return activities;
+ }
+
+ public void setActivities(List activities) {
+ this.activities = activities;
+ }
+
+ public ActivitySet watermark(String watermark) {
+ this.watermark = watermark;
+ return this;
+ }
+
+ /**
+ * Maximum watermark of activities within this set
+ * @return watermark
+ **/
+ @ApiModelProperty(value = "Maximum watermark of activities within this set")
+ public String getWatermark() {
+ return watermark;
+ }
+
+ public void setWatermark(String watermark) {
+ this.watermark = watermark;
+ }
+
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ ActivitySet activitySet = (ActivitySet) o;
+ return Objects.equals(this.activities, activitySet.activities) &&
+ Objects.equals(this.watermark, activitySet.watermark);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(activities, watermark);
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class ActivitySet {\n");
+
+ sb.append(" activities: ").append(toIndentedString(activities)).append("\n");
+ sb.append(" watermark: ").append(toIndentedString(watermark)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+}
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActivityTypes.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActivityTypes.java
new file mode 100644
index 0000000000..4657a59709
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActivityTypes.java
@@ -0,0 +1,98 @@
+/*
+ * Microsoft Bot Connector API - v3.0
+ * The Bot Connector REST API allows your bot to send and receive messages to channels configured in the [Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST and JSON over HTTPS. Client libraries for this REST API are available. See below for a list. Many bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The Bot State REST API allows a bot to store and retrieve state associated with users and conversations. Authentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is described in detail in the [Connector Authentication](/en-us/restapi/authentication) document. # Client Libraries for the Bot Connector REST API * [Bot Builder for C#](/en-us/csharp/builder/sdkreference/) * [Bot Builder for Node.js](/en-us/node/builder/overview/) * Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json) � 2016 Microsoft
+ *
+ * OpenAPI spec version: v3
+ * Contact: botframework@microsoft.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+
+package client.model;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+
+/**
+ * Types of Activities
+ */
+@JsonAdapter(ActivityTypes.Adapter.class)
+public enum ActivityTypes {
+
+ MESSAGE("message"),
+
+ DIALOGSTATE("dialogState"),
+
+ CONTACTRELATIONUPDATE("contactRelationUpdate"),
+
+ CONVERSATIONUPDATE("conversationUpdate"),
+
+ TYPING("typing"),
+
+ ENDOFCONVERSATION("endOfConversation"),
+
+ EVENT("event"),
+
+ INVOKE("invoke"),
+
+ DELETEUSERDATA("deleteUserData"),
+
+ MESSAGEUPDATE("messageUpdate"),
+
+ MESSAGEDELETE("messageDelete"),
+
+ INSTALLATIONUPDATE("installationUpdate"),
+
+ MESSAGEREACTION("messageReaction"),
+
+ SUGGESTION("suggestion"),
+
+ TRACE("trace"),
+
+ HANDOFF("handoff");
+
+ private String value;
+
+ ActivityTypes(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+ public static ActivityTypes fromValue(String text) {
+ for (ActivityTypes b : ActivityTypes.values()) {
+ if (String.valueOf(b.value).equals(text)) {
+ return b;
+ }
+ }
+ return null;
+ }
+
+ public static class Adapter extends TypeAdapter {
+ @Override
+ public void write(final JsonWriter jsonWriter, final ActivityTypes enumeration) throws IOException {
+ jsonWriter.value(enumeration.getValue());
+ }
+
+ @Override
+ public ActivityTypes read(final JsonReader jsonReader) throws IOException {
+ String value = jsonReader.nextString();
+ return ActivityTypes.fromValue(String.valueOf(value));
+ }
+ }
+}
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActivityValue.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActivityValue.java
new file mode 100644
index 0000000000..8219b3fda9
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/ActivityValue.java
@@ -0,0 +1,46 @@
+package client.model;
+
+import java.io.Serializable;
+import java.util.List;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+public class ActivityValue implements Serializable
+{
+
+ @SerializedName("ActiveTopics")
+ @Expose
+ private List activeTopics = null;
+ @SerializedName("CurrentTopic")
+ @Expose
+ private CurrentTopic currentTopic;
+ @SerializedName("Slots")
+ @Expose
+ private Slots slots;
+ private final static long serialVersionUID = 8137697838200767049L;
+
+ public List getActiveTopics() {
+ return activeTopics;
+ }
+
+ public void setActiveTopics(List activeTopics) {
+ this.activeTopics = activeTopics;
+ }
+
+ public CurrentTopic getCurrentTopic() {
+ return currentTopic;
+ }
+
+ public void setCurrentTopic(CurrentTopic currentTopic) {
+ this.currentTopic = currentTopic;
+ }
+
+ public Slots getSlots() {
+ return slots;
+ }
+
+ public void setSlots(Slots slots) {
+ this.slots = slots;
+ }
+
+}
\ No newline at end of file
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AnimationCard.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AnimationCard.java
new file mode 100644
index 0000000000..c64dbe9899
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AnimationCard.java
@@ -0,0 +1,339 @@
+/*
+ * Microsoft Bot Connector API - v3.0
+ * The Bot Connector REST API allows your bot to send and receive messages to channels configured in the [Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST and JSON over HTTPS. Client libraries for this REST API are available. See below for a list. Many bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The Bot State REST API allows a bot to store and retrieve state associated with users and conversations. Authentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is described in detail in the [Connector Authentication](/en-us/restapi/authentication) document. # Client Libraries for the Bot Connector REST API * [Bot Builder for C#](/en-us/csharp/builder/sdkreference/) * [Bot Builder for Node.js](/en-us/node/builder/overview/) * Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json) � 2016 Microsoft
+ *
+ * OpenAPI spec version: v3
+ * Contact: botframework@microsoft.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+
+package client.model;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * An animation card (Ex: gif or short video clip)
+ */
+@ApiModel(description = "An animation card (Ex: gif or short video clip)")
+@javax.annotation.Generated(value = "io.swagger.codegen.languages.JavaClientCodegen", date = "2018-08-29T10:06:15.114-07:00")
+public class AnimationCard {
+ @SerializedName("title")
+ private String title = null;
+
+ @SerializedName("subtitle")
+ private String subtitle = null;
+
+ @SerializedName("text")
+ private String text = null;
+
+ @SerializedName("image")
+ private ThumbnailUrl image = null;
+
+ @SerializedName("media")
+ private List media = null;
+
+ @SerializedName("buttons")
+ private List buttons = null;
+
+ @SerializedName("shareable")
+ private Boolean shareable = null;
+
+ @SerializedName("autoloop")
+ private Boolean autoloop = null;
+
+ @SerializedName("autostart")
+ private Boolean autostart = null;
+
+ @SerializedName("aspect")
+ private String aspect = null;
+
+ @SerializedName("value")
+ private Object value = null;
+
+ public AnimationCard title(String title) {
+ this.title = title;
+ return this;
+ }
+
+ /**
+ * Title of this card
+ * @return title
+ **/
+ @ApiModelProperty(value = "Title of this card")
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public AnimationCard subtitle(String subtitle) {
+ this.subtitle = subtitle;
+ return this;
+ }
+
+ /**
+ * Subtitle of this card
+ * @return subtitle
+ **/
+ @ApiModelProperty(value = "Subtitle of this card")
+ public String getSubtitle() {
+ return subtitle;
+ }
+
+ public void setSubtitle(String subtitle) {
+ this.subtitle = subtitle;
+ }
+
+ public AnimationCard text(String text) {
+ this.text = text;
+ return this;
+ }
+
+ /**
+ * Text of this card
+ * @return text
+ **/
+ @ApiModelProperty(value = "Text of this card")
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public AnimationCard image(ThumbnailUrl image) {
+ this.image = image;
+ return this;
+ }
+
+ /**
+ * Thumbnail placeholder
+ * @return image
+ **/
+ @ApiModelProperty(value = "Thumbnail placeholder")
+ public ThumbnailUrl getImage() {
+ return image;
+ }
+
+ public void setImage(ThumbnailUrl image) {
+ this.image = image;
+ }
+
+ public AnimationCard media(List media) {
+ this.media = media;
+ return this;
+ }
+
+ public AnimationCard addMediaItem(MediaUrl mediaItem) {
+ if (this.media == null) {
+ this.media = new ArrayList();
+ }
+ this.media.add(mediaItem);
+ return this;
+ }
+
+ /**
+ * Media URLs for this card
+ * @return media
+ **/
+ @ApiModelProperty(value = "Media URLs for this card")
+ public List getMedia() {
+ return media;
+ }
+
+ public void setMedia(List media) {
+ this.media = media;
+ }
+
+ public AnimationCard buttons(List buttons) {
+ this.buttons = buttons;
+ return this;
+ }
+
+ public AnimationCard addButtonsItem(CardAction buttonsItem) {
+ if (this.buttons == null) {
+ this.buttons = new ArrayList();
+ }
+ this.buttons.add(buttonsItem);
+ return this;
+ }
+
+ /**
+ * Actions on this card
+ * @return buttons
+ **/
+ @ApiModelProperty(value = "Actions on this card")
+ public List getButtons() {
+ return buttons;
+ }
+
+ public void setButtons(List buttons) {
+ this.buttons = buttons;
+ }
+
+ public AnimationCard shareable(Boolean shareable) {
+ this.shareable = shareable;
+ return this;
+ }
+
+ /**
+ * This content may be shared with others (default:true)
+ * @return shareable
+ **/
+ @ApiModelProperty(value = "This content may be shared with others (default:true)")
+ public Boolean isShareable() {
+ return shareable;
+ }
+
+ public void setShareable(Boolean shareable) {
+ this.shareable = shareable;
+ }
+
+ public AnimationCard autoloop(Boolean autoloop) {
+ this.autoloop = autoloop;
+ return this;
+ }
+
+ /**
+ * Should the client loop playback at end of content (default:true)
+ * @return autoloop
+ **/
+ @ApiModelProperty(value = "Should the client loop playback at end of content (default:true)")
+ public Boolean isAutoloop() {
+ return autoloop;
+ }
+
+ public void setAutoloop(Boolean autoloop) {
+ this.autoloop = autoloop;
+ }
+
+ public AnimationCard autostart(Boolean autostart) {
+ this.autostart = autostart;
+ return this;
+ }
+
+ /**
+ * Should the client automatically start playback of media in this card (default:true)
+ * @return autostart
+ **/
+ @ApiModelProperty(value = "Should the client automatically start playback of media in this card (default:true)")
+ public Boolean isAutostart() {
+ return autostart;
+ }
+
+ public void setAutostart(Boolean autostart) {
+ this.autostart = autostart;
+ }
+
+ public AnimationCard aspect(String aspect) {
+ this.aspect = aspect;
+ return this;
+ }
+
+ /**
+ * Aspect ratio of thumbnail/media placeholder, allowed values are \"16:9\" and \"4:3\"
+ * @return aspect
+ **/
+ @ApiModelProperty(value = "Aspect ratio of thumbnail/media placeholder, allowed values are \"16:9\" and \"4:3\"")
+ public String getAspect() {
+ return aspect;
+ }
+
+ public void setAspect(String aspect) {
+ this.aspect = aspect;
+ }
+
+ public AnimationCard value(Object value) {
+ this.value = value;
+ return this;
+ }
+
+ /**
+ * Supplementary parameter for this card
+ * @return value
+ **/
+ @ApiModelProperty(value = "Supplementary parameter for this card")
+ public Object getValue() {
+ return value;
+ }
+
+ public void setValue(Object value) {
+ this.value = value;
+ }
+
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ AnimationCard animationCard = (AnimationCard) o;
+ return Objects.equals(this.title, animationCard.title) &&
+ Objects.equals(this.subtitle, animationCard.subtitle) &&
+ Objects.equals(this.text, animationCard.text) &&
+ Objects.equals(this.image, animationCard.image) &&
+ Objects.equals(this.media, animationCard.media) &&
+ Objects.equals(this.buttons, animationCard.buttons) &&
+ Objects.equals(this.shareable, animationCard.shareable) &&
+ Objects.equals(this.autoloop, animationCard.autoloop) &&
+ Objects.equals(this.autostart, animationCard.autostart) &&
+ Objects.equals(this.aspect, animationCard.aspect) &&
+ Objects.equals(this.value, animationCard.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(title, subtitle, text, image, media, buttons, shareable, autoloop, autostart, aspect, value);
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class AnimationCard {\n");
+
+ sb.append(" title: ").append(toIndentedString(title)).append("\n");
+ sb.append(" subtitle: ").append(toIndentedString(subtitle)).append("\n");
+ sb.append(" text: ").append(toIndentedString(text)).append("\n");
+ sb.append(" image: ").append(toIndentedString(image)).append("\n");
+ sb.append(" media: ").append(toIndentedString(media)).append("\n");
+ sb.append(" buttons: ").append(toIndentedString(buttons)).append("\n");
+ sb.append(" shareable: ").append(toIndentedString(shareable)).append("\n");
+ sb.append(" autoloop: ").append(toIndentedString(autoloop)).append("\n");
+ sb.append(" autostart: ").append(toIndentedString(autostart)).append("\n");
+ sb.append(" aspect: ").append(toIndentedString(aspect)).append("\n");
+ sb.append(" value: ").append(toIndentedString(value)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+}
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/Attachment.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/Attachment.java
new file mode 100644
index 0000000000..3e46e0ab59
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/Attachment.java
@@ -0,0 +1,183 @@
+/*
+ * Microsoft Bot Connector API - v3.0
+ * The Bot Connector REST API allows your bot to send and receive messages to channels configured in the [Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST and JSON over HTTPS. Client libraries for this REST API are available. See below for a list. Many bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The Bot State REST API allows a bot to store and retrieve state associated with users and conversations. Authentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is described in detail in the [Connector Authentication](/en-us/restapi/authentication) document. # Client Libraries for the Bot Connector REST API * [Bot Builder for C#](/en-us/csharp/builder/sdkreference/) * [Bot Builder for Node.js](/en-us/node/builder/overview/) * Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json) � 2016 Microsoft
+ *
+ * OpenAPI spec version: v3
+ * Contact: botframework@microsoft.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+
+package client.model;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.Objects;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * An attachment within an activity
+ */
+@ApiModel(description = "An attachment within an activity")
+@javax.annotation.Generated(value = "io.swagger.codegen.languages.JavaClientCodegen", date = "2018-08-29T10:06:15.114-07:00")
+public class Attachment {
+ @SerializedName("contentType")
+ private String contentType = null;
+
+ @SerializedName("contentUrl")
+ private String contentUrl = null;
+
+ @SerializedName("content")
+ private Object content = null;
+
+ @SerializedName("name")
+ private String name = null;
+
+ @SerializedName("thumbnailUrl")
+ private String thumbnailUrl = null;
+
+ public Attachment contentType(String contentType) {
+ this.contentType = contentType;
+ return this;
+ }
+
+ /**
+ * mimetype/Contenttype for the file
+ * @return contentType
+ **/
+ @ApiModelProperty(value = "mimetype/Contenttype for the file")
+ public String getContentType() {
+ return contentType;
+ }
+
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+
+ public Attachment contentUrl(String contentUrl) {
+ this.contentUrl = contentUrl;
+ return this;
+ }
+
+ /**
+ * Content Url
+ * @return contentUrl
+ **/
+ @ApiModelProperty(value = "Content Url")
+ public String getContentUrl() {
+ return contentUrl;
+ }
+
+ public void setContentUrl(String contentUrl) {
+ this.contentUrl = contentUrl;
+ }
+
+ public Attachment content(Object content) {
+ this.content = content;
+ return this;
+ }
+
+ /**
+ * Embedded content
+ * @return content
+ **/
+ @ApiModelProperty(value = "Embedded content")
+ public Object getContent() {
+ return content;
+ }
+
+ public void setContent(Object content) {
+ this.content = content;
+ }
+
+ public Attachment name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * (OPTIONAL) The name of the attachment
+ * @return name
+ **/
+ @ApiModelProperty(value = "(OPTIONAL) The name of the attachment")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Attachment thumbnailUrl(String thumbnailUrl) {
+ this.thumbnailUrl = thumbnailUrl;
+ return this;
+ }
+
+ /**
+ * (OPTIONAL) Thumbnail associated with attachment
+ * @return thumbnailUrl
+ **/
+ @ApiModelProperty(value = "(OPTIONAL) Thumbnail associated with attachment")
+ public String getThumbnailUrl() {
+ return thumbnailUrl;
+ }
+
+ public void setThumbnailUrl(String thumbnailUrl) {
+ this.thumbnailUrl = thumbnailUrl;
+ }
+
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Attachment attachment = (Attachment) o;
+ return Objects.equals(this.contentType, attachment.contentType) &&
+ Objects.equals(this.contentUrl, attachment.contentUrl) &&
+ Objects.equals(this.content, attachment.content) &&
+ Objects.equals(this.name, attachment.name) &&
+ Objects.equals(this.thumbnailUrl, attachment.thumbnailUrl);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(contentType, contentUrl, content, name, thumbnailUrl);
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class Attachment {\n");
+
+ sb.append(" contentType: ").append(toIndentedString(contentType)).append("\n");
+ sb.append(" contentUrl: ").append(toIndentedString(contentUrl)).append("\n");
+ sb.append(" content: ").append(toIndentedString(content)).append("\n");
+ sb.append(" name: ").append(toIndentedString(name)).append("\n");
+ sb.append(" thumbnailUrl: ").append(toIndentedString(thumbnailUrl)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+}
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AttachmentData.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AttachmentData.java
new file mode 100644
index 0000000000..b36cc5112b
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AttachmentData.java
@@ -0,0 +1,160 @@
+/*
+ * Microsoft Bot Connector API - v3.0
+ * The Bot Connector REST API allows your bot to send and receive messages to channels configured in the [Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST and JSON over HTTPS. Client libraries for this REST API are available. See below for a list. Many bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The Bot State REST API allows a bot to store and retrieve state associated with users and conversations. Authentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is described in detail in the [Connector Authentication](/en-us/restapi/authentication) document. # Client Libraries for the Bot Connector REST API * [Bot Builder for C#](/en-us/csharp/builder/sdkreference/) * [Bot Builder for Node.js](/en-us/node/builder/overview/) * Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json) � 2016 Microsoft
+ *
+ * OpenAPI spec version: v3
+ * Contact: botframework@microsoft.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+
+package client.model;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.Objects;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * Attachment data
+ */
+@ApiModel(description = "Attachment data")
+@javax.annotation.Generated(value = "io.swagger.codegen.languages.JavaClientCodegen", date = "2018-08-29T10:06:15.114-07:00")
+public class AttachmentData {
+ @SerializedName("type")
+ private String type = null;
+
+ @SerializedName("name")
+ private String name = null;
+
+ @SerializedName("originalBase64")
+ private byte[] originalBase64 = null;
+
+ @SerializedName("thumbnailBase64")
+ private byte[] thumbnailBase64 = null;
+
+ public AttachmentData type(String type) {
+ this.type = type;
+ return this;
+ }
+
+ /**
+ * Content-Type of the attachment
+ * @return type
+ **/
+ @ApiModelProperty(value = "Content-Type of the attachment")
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public AttachmentData name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Name of the attachment
+ * @return name
+ **/
+ @ApiModelProperty(value = "Name of the attachment")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public AttachmentData originalBase64(byte[] originalBase64) {
+ this.originalBase64 = originalBase64;
+ return this;
+ }
+
+ /**
+ * Attachment content
+ * @return originalBase64
+ **/
+ @ApiModelProperty(value = "Attachment content")
+ public byte[] getOriginalBase64() {
+ return originalBase64;
+ }
+
+ public void setOriginalBase64(byte[] originalBase64) {
+ this.originalBase64 = originalBase64;
+ }
+
+ public AttachmentData thumbnailBase64(byte[] thumbnailBase64) {
+ this.thumbnailBase64 = thumbnailBase64;
+ return this;
+ }
+
+ /**
+ * Attachment thumbnail
+ * @return thumbnailBase64
+ **/
+ @ApiModelProperty(value = "Attachment thumbnail")
+ public byte[] getThumbnailBase64() {
+ return thumbnailBase64;
+ }
+
+ public void setThumbnailBase64(byte[] thumbnailBase64) {
+ this.thumbnailBase64 = thumbnailBase64;
+ }
+
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ AttachmentData attachmentData = (AttachmentData) o;
+ return Objects.equals(this.type, attachmentData.type) &&
+ Objects.equals(this.name, attachmentData.name) &&
+ Objects.equals(this.originalBase64, attachmentData.originalBase64) &&
+ Objects.equals(this.thumbnailBase64, attachmentData.thumbnailBase64);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, name, originalBase64, thumbnailBase64);
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class AttachmentData {\n");
+
+ sb.append(" type: ").append(toIndentedString(type)).append("\n");
+ sb.append(" name: ").append(toIndentedString(name)).append("\n");
+ sb.append(" originalBase64: ").append(toIndentedString(originalBase64)).append("\n");
+ sb.append(" thumbnailBase64: ").append(toIndentedString(thumbnailBase64)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+}
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AttachmentInfo.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AttachmentInfo.java
new file mode 100644
index 0000000000..9f6ed86787
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AttachmentInfo.java
@@ -0,0 +1,147 @@
+/*
+ * Microsoft Bot Connector API - v3.0
+ * The Bot Connector REST API allows your bot to send and receive messages to channels configured in the [Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST and JSON over HTTPS. Client libraries for this REST API are available. See below for a list. Many bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The Bot State REST API allows a bot to store and retrieve state associated with users and conversations. Authentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is described in detail in the [Connector Authentication](/en-us/restapi/authentication) document. # Client Libraries for the Bot Connector REST API * [Bot Builder for C#](/en-us/csharp/builder/sdkreference/) * [Bot Builder for Node.js](/en-us/node/builder/overview/) * Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json) � 2016 Microsoft
+ *
+ * OpenAPI spec version: v3
+ * Contact: botframework@microsoft.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+
+package client.model;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * Metadata for an attachment
+ */
+@ApiModel(description = "Metadata for an attachment")
+@javax.annotation.Generated(value = "io.swagger.codegen.languages.JavaClientCodegen", date = "2018-08-29T10:06:15.114-07:00")
+public class AttachmentInfo {
+ @SerializedName("name")
+ private String name = null;
+
+ @SerializedName("type")
+ private String type = null;
+
+ @SerializedName("views")
+ private List views = null;
+
+ public AttachmentInfo name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ /**
+ * Name of the attachment
+ * @return name
+ **/
+ @ApiModelProperty(value = "Name of the attachment")
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public AttachmentInfo type(String type) {
+ this.type = type;
+ return this;
+ }
+
+ /**
+ * ContentType of the attachment
+ * @return type
+ **/
+ @ApiModelProperty(value = "ContentType of the attachment")
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public AttachmentInfo views(List views) {
+ this.views = views;
+ return this;
+ }
+
+ public AttachmentInfo addViewsItem(AttachmentView viewsItem) {
+ if (this.views == null) {
+ this.views = new ArrayList();
+ }
+ this.views.add(viewsItem);
+ return this;
+ }
+
+ /**
+ * attachment views
+ * @return views
+ **/
+ @ApiModelProperty(value = "attachment views")
+ public List getViews() {
+ return views;
+ }
+
+ public void setViews(List views) {
+ this.views = views;
+ }
+
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ AttachmentInfo attachmentInfo = (AttachmentInfo) o;
+ return Objects.equals(this.name, attachmentInfo.name) &&
+ Objects.equals(this.type, attachmentInfo.type) &&
+ Objects.equals(this.views, attachmentInfo.views);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, type, views);
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class AttachmentInfo {\n");
+
+ sb.append(" name: ").append(toIndentedString(name)).append("\n");
+ sb.append(" type: ").append(toIndentedString(type)).append("\n");
+ sb.append(" views: ").append(toIndentedString(views)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+}
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AttachmentLayoutTypes.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AttachmentLayoutTypes.java
new file mode 100644
index 0000000000..c1ed12619e
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AttachmentLayoutTypes.java
@@ -0,0 +1,70 @@
+/*
+ * Microsoft Bot Connector API - v3.0
+ * The Bot Connector REST API allows your bot to send and receive messages to channels configured in the [Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST and JSON over HTTPS. Client libraries for this REST API are available. See below for a list. Many bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The Bot State REST API allows a bot to store and retrieve state associated with users and conversations. Authentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is described in detail in the [Connector Authentication](/en-us/restapi/authentication) document. # Client Libraries for the Bot Connector REST API * [Bot Builder for C#](/en-us/csharp/builder/sdkreference/) * [Bot Builder for Node.js](/en-us/node/builder/overview/) * Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json) � 2016 Microsoft
+ *
+ * OpenAPI spec version: v3
+ * Contact: botframework@microsoft.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+
+package client.model;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+
+/**
+ * Attachment layout types
+ */
+@JsonAdapter(AttachmentLayoutTypes.Adapter.class)
+public enum AttachmentLayoutTypes {
+
+ LIST("list"),
+
+ CAROUSEL("carousel");
+
+ private String value;
+
+ AttachmentLayoutTypes(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+ public static AttachmentLayoutTypes fromValue(String text) {
+ for (AttachmentLayoutTypes b : AttachmentLayoutTypes.values()) {
+ if (String.valueOf(b.value).equals(text)) {
+ return b;
+ }
+ }
+ return null;
+ }
+
+ public static class Adapter extends TypeAdapter {
+ @Override
+ public void write(final JsonWriter jsonWriter, final AttachmentLayoutTypes enumeration) throws IOException {
+ jsonWriter.value(enumeration.getValue());
+ }
+
+ @Override
+ public AttachmentLayoutTypes read(final JsonReader jsonReader) throws IOException {
+ String value = jsonReader.nextString();
+ return AttachmentLayoutTypes.fromValue(String.valueOf(value));
+ }
+ }
+}
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AttachmentView.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AttachmentView.java
new file mode 100644
index 0000000000..dd61899429
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AttachmentView.java
@@ -0,0 +1,114 @@
+/*
+ * Microsoft Bot Connector API - v3.0
+ * The Bot Connector REST API allows your bot to send and receive messages to channels configured in the [Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST and JSON over HTTPS. Client libraries for this REST API are available. See below for a list. Many bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The Bot State REST API allows a bot to store and retrieve state associated with users and conversations. Authentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is described in detail in the [Connector Authentication](/en-us/restapi/authentication) document. # Client Libraries for the Bot Connector REST API * [Bot Builder for C#](/en-us/csharp/builder/sdkreference/) * [Bot Builder for Node.js](/en-us/node/builder/overview/) * Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json) � 2016 Microsoft
+ *
+ * OpenAPI spec version: v3
+ * Contact: botframework@microsoft.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+
+package client.model;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.Objects;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * Attachment View name and size
+ */
+@ApiModel(description = "Attachment View name and size")
+@javax.annotation.Generated(value = "io.swagger.codegen.languages.JavaClientCodegen", date = "2018-08-29T10:06:15.114-07:00")
+public class AttachmentView {
+ @SerializedName("viewId")
+ private String viewId = null;
+
+ @SerializedName("size")
+ private Integer size = null;
+
+ public AttachmentView viewId(String viewId) {
+ this.viewId = viewId;
+ return this;
+ }
+
+ /**
+ * Content type of the attachment
+ * @return viewId
+ **/
+ @ApiModelProperty(value = "Content type of the attachment")
+ public String getViewId() {
+ return viewId;
+ }
+
+ public void setViewId(String viewId) {
+ this.viewId = viewId;
+ }
+
+ public AttachmentView size(Integer size) {
+ this.size = size;
+ return this;
+ }
+
+ /**
+ * Name of the attachment
+ * @return size
+ **/
+ @ApiModelProperty(value = "Name of the attachment")
+ public Integer getSize() {
+ return size;
+ }
+
+ public void setSize(Integer size) {
+ this.size = size;
+ }
+
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ AttachmentView attachmentView = (AttachmentView) o;
+ return Objects.equals(this.viewId, attachmentView.viewId) &&
+ Objects.equals(this.size, attachmentView.size);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(viewId, size);
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class AttachmentView {\n");
+
+ sb.append(" viewId: ").append(toIndentedString(viewId)).append("\n");
+ sb.append(" size: ").append(toIndentedString(size)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+}
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AudioCard.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AudioCard.java
new file mode 100644
index 0000000000..5c858e530c
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/AudioCard.java
@@ -0,0 +1,339 @@
+/*
+ * Microsoft Bot Connector API - v3.0
+ * The Bot Connector REST API allows your bot to send and receive messages to channels configured in the [Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST and JSON over HTTPS. Client libraries for this REST API are available. See below for a list. Many bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The Bot State REST API allows a bot to store and retrieve state associated with users and conversations. Authentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is described in detail in the [Connector Authentication](/en-us/restapi/authentication) document. # Client Libraries for the Bot Connector REST API * [Bot Builder for C#](/en-us/csharp/builder/sdkreference/) * [Bot Builder for Node.js](/en-us/node/builder/overview/) * Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json) � 2016 Microsoft
+ *
+ * OpenAPI spec version: v3
+ * Contact: botframework@microsoft.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+
+package client.model;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * Audio card
+ */
+@ApiModel(description = "Audio card")
+@javax.annotation.Generated(value = "io.swagger.codegen.languages.JavaClientCodegen", date = "2018-08-29T10:06:15.114-07:00")
+public class AudioCard {
+ @SerializedName("title")
+ private String title = null;
+
+ @SerializedName("subtitle")
+ private String subtitle = null;
+
+ @SerializedName("text")
+ private String text = null;
+
+ @SerializedName("image")
+ private ThumbnailUrl image = null;
+
+ @SerializedName("media")
+ private List media = null;
+
+ @SerializedName("buttons")
+ private List buttons = null;
+
+ @SerializedName("shareable")
+ private Boolean shareable = null;
+
+ @SerializedName("autoloop")
+ private Boolean autoloop = null;
+
+ @SerializedName("autostart")
+ private Boolean autostart = null;
+
+ @SerializedName("aspect")
+ private String aspect = null;
+
+ @SerializedName("value")
+ private Object value = null;
+
+ public AudioCard title(String title) {
+ this.title = title;
+ return this;
+ }
+
+ /**
+ * Title of this card
+ * @return title
+ **/
+ @ApiModelProperty(value = "Title of this card")
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public AudioCard subtitle(String subtitle) {
+ this.subtitle = subtitle;
+ return this;
+ }
+
+ /**
+ * Subtitle of this card
+ * @return subtitle
+ **/
+ @ApiModelProperty(value = "Subtitle of this card")
+ public String getSubtitle() {
+ return subtitle;
+ }
+
+ public void setSubtitle(String subtitle) {
+ this.subtitle = subtitle;
+ }
+
+ public AudioCard text(String text) {
+ this.text = text;
+ return this;
+ }
+
+ /**
+ * Text of this card
+ * @return text
+ **/
+ @ApiModelProperty(value = "Text of this card")
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public AudioCard image(ThumbnailUrl image) {
+ this.image = image;
+ return this;
+ }
+
+ /**
+ * Thumbnail placeholder
+ * @return image
+ **/
+ @ApiModelProperty(value = "Thumbnail placeholder")
+ public ThumbnailUrl getImage() {
+ return image;
+ }
+
+ public void setImage(ThumbnailUrl image) {
+ this.image = image;
+ }
+
+ public AudioCard media(List media) {
+ this.media = media;
+ return this;
+ }
+
+ public AudioCard addMediaItem(MediaUrl mediaItem) {
+ if (this.media == null) {
+ this.media = new ArrayList();
+ }
+ this.media.add(mediaItem);
+ return this;
+ }
+
+ /**
+ * Media URLs for this card
+ * @return media
+ **/
+ @ApiModelProperty(value = "Media URLs for this card")
+ public List getMedia() {
+ return media;
+ }
+
+ public void setMedia(List media) {
+ this.media = media;
+ }
+
+ public AudioCard buttons(List buttons) {
+ this.buttons = buttons;
+ return this;
+ }
+
+ public AudioCard addButtonsItem(CardAction buttonsItem) {
+ if (this.buttons == null) {
+ this.buttons = new ArrayList();
+ }
+ this.buttons.add(buttonsItem);
+ return this;
+ }
+
+ /**
+ * Actions on this card
+ * @return buttons
+ **/
+ @ApiModelProperty(value = "Actions on this card")
+ public List getButtons() {
+ return buttons;
+ }
+
+ public void setButtons(List buttons) {
+ this.buttons = buttons;
+ }
+
+ public AudioCard shareable(Boolean shareable) {
+ this.shareable = shareable;
+ return this;
+ }
+
+ /**
+ * This content may be shared with others (default:true)
+ * @return shareable
+ **/
+ @ApiModelProperty(value = "This content may be shared with others (default:true)")
+ public Boolean isShareable() {
+ return shareable;
+ }
+
+ public void setShareable(Boolean shareable) {
+ this.shareable = shareable;
+ }
+
+ public AudioCard autoloop(Boolean autoloop) {
+ this.autoloop = autoloop;
+ return this;
+ }
+
+ /**
+ * Should the client loop playback at end of content (default:true)
+ * @return autoloop
+ **/
+ @ApiModelProperty(value = "Should the client loop playback at end of content (default:true)")
+ public Boolean isAutoloop() {
+ return autoloop;
+ }
+
+ public void setAutoloop(Boolean autoloop) {
+ this.autoloop = autoloop;
+ }
+
+ public AudioCard autostart(Boolean autostart) {
+ this.autostart = autostart;
+ return this;
+ }
+
+ /**
+ * Should the client automatically start playback of media in this card (default:true)
+ * @return autostart
+ **/
+ @ApiModelProperty(value = "Should the client automatically start playback of media in this card (default:true)")
+ public Boolean isAutostart() {
+ return autostart;
+ }
+
+ public void setAutostart(Boolean autostart) {
+ this.autostart = autostart;
+ }
+
+ public AudioCard aspect(String aspect) {
+ this.aspect = aspect;
+ return this;
+ }
+
+ /**
+ * Aspect ratio of thumbnail/media placeholder, allowed values are \"16:9\" and \"4:3\"
+ * @return aspect
+ **/
+ @ApiModelProperty(value = "Aspect ratio of thumbnail/media placeholder, allowed values are \"16:9\" and \"4:3\"")
+ public String getAspect() {
+ return aspect;
+ }
+
+ public void setAspect(String aspect) {
+ this.aspect = aspect;
+ }
+
+ public AudioCard value(Object value) {
+ this.value = value;
+ return this;
+ }
+
+ /**
+ * Supplementary parameter for this card
+ * @return value
+ **/
+ @ApiModelProperty(value = "Supplementary parameter for this card")
+ public Object getValue() {
+ return value;
+ }
+
+ public void setValue(Object value) {
+ this.value = value;
+ }
+
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ AudioCard audioCard = (AudioCard) o;
+ return Objects.equals(this.title, audioCard.title) &&
+ Objects.equals(this.subtitle, audioCard.subtitle) &&
+ Objects.equals(this.text, audioCard.text) &&
+ Objects.equals(this.image, audioCard.image) &&
+ Objects.equals(this.media, audioCard.media) &&
+ Objects.equals(this.buttons, audioCard.buttons) &&
+ Objects.equals(this.shareable, audioCard.shareable) &&
+ Objects.equals(this.autoloop, audioCard.autoloop) &&
+ Objects.equals(this.autostart, audioCard.autostart) &&
+ Objects.equals(this.aspect, audioCard.aspect) &&
+ Objects.equals(this.value, audioCard.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(title, subtitle, text, image, media, buttons, shareable, autoloop, autostart, aspect, value);
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class AudioCard {\n");
+
+ sb.append(" title: ").append(toIndentedString(title)).append("\n");
+ sb.append(" subtitle: ").append(toIndentedString(subtitle)).append("\n");
+ sb.append(" text: ").append(toIndentedString(text)).append("\n");
+ sb.append(" image: ").append(toIndentedString(image)).append("\n");
+ sb.append(" media: ").append(toIndentedString(media)).append("\n");
+ sb.append(" buttons: ").append(toIndentedString(buttons)).append("\n");
+ sb.append(" shareable: ").append(toIndentedString(shareable)).append("\n");
+ sb.append(" autoloop: ").append(toIndentedString(autoloop)).append("\n");
+ sb.append(" autostart: ").append(toIndentedString(autostart)).append("\n");
+ sb.append(" aspect: ").append(toIndentedString(aspect)).append("\n");
+ sb.append(" value: ").append(toIndentedString(value)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+}
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/BasicCard.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/BasicCard.java
new file mode 100644
index 0000000000..d94d00bacb
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/BasicCard.java
@@ -0,0 +1,224 @@
+/*
+ * Microsoft Bot Connector API - v3.0
+ * The Bot Connector REST API allows your bot to send and receive messages to channels configured in the [Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST and JSON over HTTPS. Client libraries for this REST API are available. See below for a list. Many bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The Bot State REST API allows a bot to store and retrieve state associated with users and conversations. Authentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is described in detail in the [Connector Authentication](/en-us/restapi/authentication) document. # Client Libraries for the Bot Connector REST API * [Bot Builder for C#](/en-us/csharp/builder/sdkreference/) * [Bot Builder for Node.js](/en-us/node/builder/overview/) * Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json) � 2016 Microsoft
+ *
+ * OpenAPI spec version: v3
+ * Contact: botframework@microsoft.com
+ *
+ * NOTE: This class is auto generated by the swagger code generator program.
+ * https://github.com/swagger-api/swagger-codegen.git
+ * Do not edit the class manually.
+ */
+
+
+package client.model;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * A basic card
+ */
+@ApiModel(description = "A basic card")
+@javax.annotation.Generated(value = "io.swagger.codegen.languages.JavaClientCodegen", date = "2018-08-29T10:06:15.114-07:00")
+public class BasicCard {
+ @SerializedName("title")
+ private String title = null;
+
+ @SerializedName("subtitle")
+ private String subtitle = null;
+
+ @SerializedName("text")
+ private String text = null;
+
+ @SerializedName("images")
+ private List images = null;
+
+ @SerializedName("buttons")
+ private List buttons = null;
+
+ @SerializedName("tap")
+ private CardAction tap = null;
+
+ public BasicCard title(String title) {
+ this.title = title;
+ return this;
+ }
+
+ /**
+ * Title of the card
+ * @return title
+ **/
+ @ApiModelProperty(value = "Title of the card")
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public BasicCard subtitle(String subtitle) {
+ this.subtitle = subtitle;
+ return this;
+ }
+
+ /**
+ * Subtitle of the card
+ * @return subtitle
+ **/
+ @ApiModelProperty(value = "Subtitle of the card")
+ public String getSubtitle() {
+ return subtitle;
+ }
+
+ public void setSubtitle(String subtitle) {
+ this.subtitle = subtitle;
+ }
+
+ public BasicCard text(String text) {
+ this.text = text;
+ return this;
+ }
+
+ /**
+ * Text for the card
+ * @return text
+ **/
+ @ApiModelProperty(value = "Text for the card")
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public BasicCard images(List images) {
+ this.images = images;
+ return this;
+ }
+
+ public BasicCard addImagesItem(CardImage imagesItem) {
+ if (this.images == null) {
+ this.images = new ArrayList();
+ }
+ this.images.add(imagesItem);
+ return this;
+ }
+
+ /**
+ * Array of images for the card
+ * @return images
+ **/
+ @ApiModelProperty(value = "Array of images for the card")
+ public List getImages() {
+ return images;
+ }
+
+ public void setImages(List images) {
+ this.images = images;
+ }
+
+ public BasicCard buttons(List buttons) {
+ this.buttons = buttons;
+ return this;
+ }
+
+ public BasicCard addButtonsItem(CardAction buttonsItem) {
+ if (this.buttons == null) {
+ this.buttons = new ArrayList();
+ }
+ this.buttons.add(buttonsItem);
+ return this;
+ }
+
+ /**
+ * Set of actions applicable to the current card
+ * @return buttons
+ **/
+ @ApiModelProperty(value = "Set of actions applicable to the current card")
+ public List getButtons() {
+ return buttons;
+ }
+
+ public void setButtons(List buttons) {
+ this.buttons = buttons;
+ }
+
+ public BasicCard tap(CardAction tap) {
+ this.tap = tap;
+ return this;
+ }
+
+ /**
+ * This action will be activated when user taps on the card itself
+ * @return tap
+ **/
+ @ApiModelProperty(value = "This action will be activated when user taps on the card itself")
+ public CardAction getTap() {
+ return tap;
+ }
+
+ public void setTap(CardAction tap) {
+ this.tap = tap;
+ }
+
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ BasicCard basicCard = (BasicCard) o;
+ return Objects.equals(this.title, basicCard.title) &&
+ Objects.equals(this.subtitle, basicCard.subtitle) &&
+ Objects.equals(this.text, basicCard.text) &&
+ Objects.equals(this.images, basicCard.images) &&
+ Objects.equals(this.buttons, basicCard.buttons) &&
+ Objects.equals(this.tap, basicCard.tap);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(title, subtitle, text, images, buttons, tap);
+ }
+
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class BasicCard {\n");
+
+ sb.append(" title: ").append(toIndentedString(title)).append("\n");
+ sb.append(" subtitle: ").append(toIndentedString(subtitle)).append("\n");
+ sb.append(" text: ").append(toIndentedString(text)).append("\n");
+ sb.append(" images: ").append(toIndentedString(images)).append("\n");
+ sb.append(" buttons: ").append(toIndentedString(buttons)).append("\n");
+ sb.append(" tap: ").append(toIndentedString(tap)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces
+ * (except the first line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+}
+
diff --git a/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/BotConnectorActivity.java b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/BotConnectorActivity.java
new file mode 100644
index 0000000000..60513f694a
--- /dev/null
+++ b/solutions/android/VirtualAssistantClient/directlinespeech/src/main/java/client/model/BotConnectorActivity.java
@@ -0,0 +1,246 @@
+
+package client.model;
+
+import java.util.List;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+public class BotConnectorActivity {
+
+ @SerializedName("attachmentLayout")
+ @Expose
+ private String attachmentLayout;
+
+ public String getAttachmentLayout() {
+ return attachmentLayout;
+ }
+
+ public void setAttachmentLayout(String attachmentLayout) {
+ this.attachmentLayout = attachmentLayout;
+ }
+
+ @SerializedName("attachments")
+ @Expose
+ private List