From 2d2a4804f7790f41a8b9af373c5274ff73e7c84c Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Thu, 6 Sep 2018 17:49:52 -0300
Subject: [PATCH 01/56] Add android settings screen. Add AEC setting

---
 android/app/src/main/AndroidManifest.xml      | 10 +++
 android/app/src/main/cpp/native.cpp           | 46 ++++++++++++++
 .../hifiinterface/InterfaceActivity.java      |  7 +++
 .../hifiinterface/MainActivity.java           | 23 +++++++
 .../fragment/SettingsFragment.java            | 63 +++++++++++++++++++
 .../receiver/HeadsetStateReceiver.java        | 20 ++++++
 .../app/src/main/res/menu/menu_navigation.xml |  5 ++
 android/app/src/main/res/values/strings.xml   |  5 ++
 android/app/src/main/res/xml/settings.xml     | 11 ++++
 interface/src/AndroidHelper.cpp               | 17 +++++
 interface/src/AndroidHelper.h                 |  1 +
 libraries/audio-client/src/AudioClient.cpp    |  6 +-
 libraries/audio-client/src/AudioClient.h      |  8 +++
 13 files changed, 220 insertions(+), 2 deletions(-)
 create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java
 create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java
 create mode 100644 android/app/src/main/res/xml/settings.xml

diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 7255e1f295..b216819ed0 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -9,6 +9,7 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
     <uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true"/>
     <uses-feature android:name="android.hardware.sensor.gyroscope" android:required="true"/>
 
@@ -75,6 +76,15 @@
             android:enabled="true"
             android:exported="false"
             android:process=":breakpad_uploader"/>
+
+        <receiver
+            android:name=".receiver.HeadsetStateReceiver"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.HEADSET_PLUG" />
+            </intent-filter>
+        </receiver>
     </application>
 
     <uses-feature android:name="android.software.vr.mode" android:required="true"/>
diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp
index ce5af01f29..6b44b2dc7a 100644
--- a/android/app/src/main/cpp/native.cpp
+++ b/android/app/src/main/cpp/native.cpp
@@ -355,5 +355,51 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_WebViewActivity_nativeProcessU
     AndroidHelper::instance().processURL(QString::fromUtf8(nativeString));
 }
 
+JNIEXPORT void JNICALL
+Java_io_highfidelity_hifiinterface_fragment_SettingsFragment_updateHifiSetting(JNIEnv *env,
+                                                                               jobject instance,
+                                                                               jstring group_,
+                                                                               jstring key_,
+                                                                               jboolean value_) {
+    const char *c_group = env->GetStringUTFChars(group_, 0);
+    const char *c_key = env->GetStringUTFChars(key_, 0);
+
+    const QString group = QString::fromUtf8(c_group);
+    const QString key = QString::fromUtf8(c_key);
+
+    env->ReleaseStringUTFChars(group_, c_group);
+    env->ReleaseStringUTFChars(key_, c_key);
+
+    bool value = value_;
+
+    Setting::Handle<bool> setting { QStringList() << group << key , !value };
+    setting.set(value);
+}
+
+JNIEXPORT jboolean JNICALL
+Java_io_highfidelity_hifiinterface_fragment_SettingsFragment_getHifiSettingBoolean(JNIEnv *env,
+                                                                                   jobject instance,
+                                                                                   jstring group_,
+                                                                                   jstring key_,
+                                                                                   jboolean defaultValue) {
+    const char *c_group = env->GetStringUTFChars(group_, 0);
+    const char *c_key = env->GetStringUTFChars(key_, 0);
+
+    const QString group = QString::fromUtf8(c_group);
+    const QString key = QString::fromUtf8(c_key);
+
+    env->ReleaseStringUTFChars(group_, c_group);
+    env->ReleaseStringUTFChars(key_, c_key);
+
+    Setting::Handle<bool> setting { QStringList() << group << key , defaultValue};
+    return setting.get();
+}
+
+JNIEXPORT void JNICALL
+Java_io_highfidelity_hifiinterface_receiver_HeadsetStateReceiver_notifyHeadsetOn(JNIEnv *env,
+                                                                                 jobject instance,
+                                                                                 jboolean pluggedIn) {
+    AndroidHelper::instance().notifyHeadsetOn(pluggedIn);
+}
 
 }
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java
index f161783d6a..08e66a2f42 100644
--- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java
@@ -13,6 +13,7 @@ package io.highfidelity.hifiinterface;
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -40,6 +41,7 @@ import java.util.HashMap;
 import java.util.List;
 
 import io.highfidelity.hifiinterface.fragment.WebViewFragment;
+import io.highfidelity.hifiinterface.receiver.HeadsetStateReceiver;
 
 /*import com.google.vr.cardboard.DisplaySynchronizer;
 import com.google.vr.cardboard.DisplayUtils;
@@ -55,6 +57,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
     private static final int NORMAL_DPI = 160;
 
     private Vibrator mVibrator;
+    private HeadsetStateReceiver headsetStateReceiver;
 
     //public static native void handleHifiURL(String hifiURLString);
     private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager);
@@ -151,6 +154,8 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
         layoutParams.resolveLayoutDirection(View.LAYOUT_DIRECTION_RTL);
         qtLayout.addView(webSlidingDrawer, layoutParams);
         webSlidingDrawer.setVisibility(View.GONE);
+
+        headsetStateReceiver = new HeadsetStateReceiver();
     }
 
     @Override
@@ -161,6 +166,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
         } else {
             nativeEnterBackground();
         }
+        unregisterReceiver(headsetStateReceiver);
         //gvrApi.pauseTracking();
     }
 
@@ -183,6 +189,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
         nativeEnterForeground();
         surfacesWorkaround();
         keepInterfaceRunning = false;
+        registerReceiver(headsetStateReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
         //gvrApi.resumeTracking();
     }
 
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
index db6f0fca24..4c6d05a3e8 100644
--- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
@@ -33,6 +33,7 @@ import io.highfidelity.hifiinterface.fragment.FriendsFragment;
 import io.highfidelity.hifiinterface.fragment.HomeFragment;
 import io.highfidelity.hifiinterface.fragment.LoginFragment;
 import io.highfidelity.hifiinterface.fragment.PolicyFragment;
+import io.highfidelity.hifiinterface.fragment.SettingsFragment;
 import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
 
 public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
@@ -80,6 +81,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
 
         mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people);
 
+        updateDebugMenu(mNavigationView.getMenu());
+
         Toolbar toolbar = findViewById(R.id.toolbar);
         toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle);
         setSupportActionBar(toolbar);
@@ -108,6 +111,16 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
         }
     }
 
+    private void updateDebugMenu(Menu menu) {
+        if (BuildConfig.DEBUG) {
+            for (int i=0; i < menu.size(); i++) {
+                if (menu.getItem(i).getItemId() == R.id.action_debug_settings) {
+                    menu.getItem(i).setVisible(true);
+                }
+            }
+        }
+    }
+
     private void loadFragment(String fragment) {
         switch (fragment) {
             case "Login":
@@ -151,6 +164,13 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
         loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true);
     }
 
+    private void loadSettingsFragment() {
+        SettingsFragment fragment = SettingsFragment.newInstance();
+
+        loadFragment(fragment, getString(R.string.settings), getString(R.string.tagSettings), true);
+    }
+
+
     private void loadFragment(Fragment fragment, String title, String tag, boolean addToBackStack) {
         FragmentManager fragmentManager = getFragmentManager();
 
@@ -241,6 +261,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
             case R.id.action_people:
                 loadPeopleFragment();
                 return true;
+            case R.id.action_debug_settings:
+                loadSettingsFragment();
+                return true;
         }
         return false;
     }
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java
new file mode 100644
index 0000000000..cc23665e72
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java
@@ -0,0 +1,63 @@
+package io.highfidelity.hifiinterface.fragment;
+
+import android.content.SharedPreferences;
+import android.media.audiofx.AcousticEchoCanceler;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.support.annotation.Nullable;
+
+import io.highfidelity.hifiinterface.R;
+
+public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
+
+    public native void updateHifiSetting(String group, String key, boolean value);
+    public native boolean getHifiSettingBoolean(String group, String key, boolean defaultValue);
+
+    private final String HIFI_SETTINGS_ANDROID_GROUP = "Android";
+    private final String HIFI_SETTINGS_AEC_KEY = "aec";
+    private final String PREFERENCE_KEY_AEC = "aec";
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.settings);
+
+        if (!AcousticEchoCanceler.isAvailable()) {
+            getPreferenceScreen().getPreferenceManager().findPreference("aec").setEnabled(false);
+        }
+
+        getPreferenceScreen().getSharedPreferences().edit().putBoolean(PREFERENCE_KEY_AEC,
+                getHifiSettingBoolean(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, false));
+    }
+
+    public static SettingsFragment newInstance() {
+        SettingsFragment fragment = new SettingsFragment();
+        return fragment;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+        Preference pref = findPreference(key);
+        switch (key) {
+            case "aec":
+                updateHifiSetting(HIFI_SETTINGS_ANDROID_GROUP, HIFI_SETTINGS_AEC_KEY, sharedPreferences.getBoolean(key, false));
+                break;
+            default:
+                break;
+        }
+    }
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java
new file mode 100644
index 0000000000..29bc1c49f2
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java
@@ -0,0 +1,20 @@
+package io.highfidelity.hifiinterface.receiver;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.util.Log;
+
+public class HeadsetStateReceiver extends BroadcastReceiver {
+
+    private native void notifyHeadsetOn(boolean pluggedIn);
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+        Log.d("[HEADSET] " , "BR - Wired headset on:" +  audioManager.isWiredHeadsetOn());
+        notifyHeadsetOn(audioManager.isWiredHeadsetOn());
+    }
+}
diff --git a/android/app/src/main/res/menu/menu_navigation.xml b/android/app/src/main/res/menu/menu_navigation.xml
index 3cce64f9f5..142af5d146 100644
--- a/android/app/src/main/res/menu/menu_navigation.xml
+++ b/android/app/src/main/res/menu/menu_navigation.xml
@@ -9,4 +9,9 @@
         android:id="@+id/action_people"
         android:title="@string/people"
         />
+    <item
+        android:id="@+id/action_debug_settings"
+        android:title="@string/settings"
+        android:visible="false"
+    />
 </menu>
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index b158aba59d..abde15f484 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -29,4 +29,9 @@
     <string name="tagFragmentLogin">tagFragmentLogin</string>
     <string name="tagFragmentPolicy">tagFragmentPolicy</string>
     <string name="tagFragmentPeople">tagFragmentPeople</string>
+    <string name="tagSettings">tagSettings</string>
+    <string name="settings">Settings</string>
+    <string name="AEC">AEC</string>
+    <string name="acoustic_echo_cancellation">Acoustic Echo Cancellation</string>
+    <string name="settings_developer">Developer</string>
 </resources>
diff --git a/android/app/src/main/res/xml/settings.xml b/android/app/src/main/res/xml/settings.xml
new file mode 100644
index 0000000000..5ec47b1aff
--- /dev/null
+++ b/android/app/src/main/res/xml/settings.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+    <PreferenceCategory
+        android:title="@string/settings_developer"
+        android:key="pref_key_developer">
+        <SwitchPreference
+            android:key="aec"
+            android:title="@string/AEC"
+            android:summary="@string/acoustic_echo_cancellation" />
+    </PreferenceCategory>
+</PreferenceScreen>
\ No newline at end of file
diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp
index 419382f2cb..35bf094591 100644
--- a/interface/src/AndroidHelper.cpp
+++ b/interface/src/AndroidHelper.cpp
@@ -10,6 +10,7 @@
 //
 #include "AndroidHelper.h"
 #include <QDebug>
+#include <AudioClient.h>
 #include "Application.h"
 
 #if defined(qApp)
@@ -18,6 +19,7 @@
 #define qApp (static_cast<Application*>(QCoreApplication::instance()))
 
 AndroidHelper::AndroidHelper() {
+    qRegisterMetaType<QAudio::Mode>("QAudio::Mode");
 }
 
 AndroidHelper::~AndroidHelper() {
@@ -56,3 +58,18 @@ void AndroidHelper::processURL(const QString &url) {
         qApp->acceptURL(url);
     }
 }
+
+void AndroidHelper::notifyHeadsetOn(bool pluggedIn) {
+#if defined (Q_OS_ANDROID)
+    auto audioClient = DependencyManager::get<AudioClient>();
+    if (audioClient) {
+        QAudioDeviceInfo activeDev =  audioClient->getActiveAudioDevice(QAudio::AudioInput);
+        Setting::Handle<bool> enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false);
+        if ((pluggedIn || !enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_RECOGNITION) {
+            QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_RECOGNITION));
+        } else if ( (!pluggedIn && enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_COMMUNICATION) {
+            QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_COMMUNICATION));
+        }
+    }
+#endif
+}
diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h
index 03d92f91d9..11b84e4025 100644
--- a/interface/src/AndroidHelper.h
+++ b/interface/src/AndroidHelper.h
@@ -29,6 +29,7 @@ public:
 
     void performHapticFeedback(int duration);
     void processURL(const QString &url);
+    void notifyHeadsetOn(bool pluggedIn);
 
     AndroidHelper(AndroidHelper const&)  = delete;
     void operator=(AndroidHelper const&) = delete;
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index 6a9363f309..e23b8ac3cd 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -53,7 +53,6 @@
 #include "AudioHelpers.h"
 
 #if defined(Q_OS_ANDROID)
-#define VOICE_RECOGNITION "voicerecognition"
 #include <QtAndroidExtras/QAndroidJniObject>
 #endif
 
@@ -451,9 +450,12 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
 
 #if defined (Q_OS_ANDROID)
     if (mode == QAudio::AudioInput) {
+        Setting::Handle<bool> enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false);
+        bool aecEnabled = enableAEC.get();
         auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
         for (auto inputDevice : inputDevices) {
-            if (inputDevice.deviceName() == VOICE_RECOGNITION) {
+            if ((aecEnabled && inputDevice.deviceName() == VOICE_COMMUNICATION) ||
+                    (!aecEnabled && inputDevice.deviceName() == VOICE_RECOGNITION)) {
                 return inputDevice;
             }
         }
diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
index 8599c990a3..fa7ac40a16 100644
--- a/libraries/audio-client/src/AudioClient.h
+++ b/libraries/audio-client/src/AudioClient.h
@@ -64,6 +64,14 @@
 #pragma warning( pop )
 #endif
 
+#if defined (Q_OS_ANDROID)
+#define VOICE_RECOGNITION "voicerecognition"
+#define VOICE_COMMUNICATION "voicecommunication"
+
+#define ANDROID_SETTINGS_GROUP "Android"
+#define SETTING_AEC_KEY "aec"
+#endif
+
 class QAudioInput;
 class QAudioOutput;
 class QIODevice;

From 2943502c9b5d0d9630c97b76793ffa5b3c205561 Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Wed, 12 Sep 2018 16:36:04 -0300
Subject: [PATCH 02/56] Headset plug/unplug detection

---
 .../receiver/HeadsetStateReceiver.java        |  2 --
 interface/src/AndroidHelper.cpp               |  8 +----
 libraries/audio-client/src/AudioClient.cpp    | 32 +++++++++++++++++--
 libraries/audio-client/src/AudioClient.h      | 11 +++++--
 4 files changed, 39 insertions(+), 14 deletions(-)

diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java
index 29bc1c49f2..5645912d73 100644
--- a/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java
@@ -13,8 +13,6 @@ public class HeadsetStateReceiver extends BroadcastReceiver {
     @Override
     public void onReceive(Context context, Intent intent) {
         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-
-        Log.d("[HEADSET] " , "BR - Wired headset on:" +  audioManager.isWiredHeadsetOn());
         notifyHeadsetOn(audioManager.isWiredHeadsetOn());
     }
 }
diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp
index 35bf094591..400085a62a 100644
--- a/interface/src/AndroidHelper.cpp
+++ b/interface/src/AndroidHelper.cpp
@@ -63,13 +63,7 @@ void AndroidHelper::notifyHeadsetOn(bool pluggedIn) {
 #if defined (Q_OS_ANDROID)
     auto audioClient = DependencyManager::get<AudioClient>();
     if (audioClient) {
-        QAudioDeviceInfo activeDev =  audioClient->getActiveAudioDevice(QAudio::AudioInput);
-        Setting::Handle<bool> enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false);
-        if ((pluggedIn || !enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_RECOGNITION) {
-            QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_RECOGNITION));
-        } else if ( (!pluggedIn && enableAEC.get()) && !activeDev.isNull() && activeDev.deviceName() != VOICE_COMMUNICATION) {
-            QMetaObject::invokeMethod(audioClient.get(), "switchAudioDevice", Q_ARG(QAudio::Mode, QAudio::AudioInput), Q_ARG(QString, VOICE_COMMUNICATION));
-        }
+        QMetaObject::invokeMethod(audioClient.data(), "setHeadsetPluggedIn", Q_ARG(bool, pluggedIn));
     }
 #endif
 }
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index e23b8ac3cd..f0ba0307cc 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -209,6 +209,7 @@ AudioClient::AudioClient() :
     _positionGetter(DEFAULT_POSITION_GETTER),
 #if defined(Q_OS_ANDROID)
     _checkInputTimer(this),
+    _isHeadsetPluggedIn(false),
 #endif
     _orientationGetter(DEFAULT_ORIENTATION_GETTER) {
     // avoid putting a lock in the device callback
@@ -450,12 +451,14 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
 
 #if defined (Q_OS_ANDROID)
     if (mode == QAudio::AudioInput) {
-        Setting::Handle<bool> enableAEC(QStringList() << ANDROID_SETTINGS_GROUP << SETTING_AEC_KEY, false);
+        Setting::Handle<bool> enableAEC(SETTING_AEC_KEY, false);
         bool aecEnabled = enableAEC.get();
+        auto audioClient = DependencyManager::get<AudioClient>();
+        bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false ;
         auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
         for (auto inputDevice : inputDevices) {
-            if ((aecEnabled && inputDevice.deviceName() == VOICE_COMMUNICATION) ||
-                    (!aecEnabled && inputDevice.deviceName() == VOICE_RECOGNITION)) {
+            if (((headsetOn || !aecEnabled) && inputDevice.deviceName() == VOICE_RECOGNITION) ||
+                    ((!headsetOn && aecEnabled) && inputDevice.deviceName() == VOICE_COMMUNICATION)) {
                 return inputDevice;
             }
         }
@@ -1632,6 +1635,29 @@ void AudioClient::checkInputTimeout() {
 #endif
 }
 
+void AudioClient::setHeadsetPluggedIn(bool pluggedIn) {
+#if defined(Q_OS_ANDROID)
+    if (pluggedIn == !_isHeadsetPluggedIn && !_inputDeviceInfo.isNull()) {
+        QAndroidJniObject brand =  QAndroidJniObject::getStaticObjectField<jstring>("android/os/Build", "BRAND");
+        // some samsung phones needs more time to shutdown the previous input device
+        if (brand.toString().contains("samsung", Qt::CaseInsensitive)) {
+            switchInputToAudioDevice(QAudioDeviceInfo(), true);
+            QThread::msleep(200);
+        }
+
+        Setting::Handle<bool> enableAEC(SETTING_AEC_KEY, false);
+        bool aecEnabled = enableAEC.get();
+
+        if ((pluggedIn || !aecEnabled) && _inputDeviceInfo.deviceName() != VOICE_RECOGNITION) {
+            switchAudioDevice(QAudio::AudioInput, VOICE_RECOGNITION);
+        } else if (!pluggedIn && aecEnabled && _inputDeviceInfo.deviceName() != VOICE_COMMUNICATION) {
+            switchAudioDevice(QAudio::AudioInput, VOICE_COMMUNICATION);
+        }
+    }
+    _isHeadsetPluggedIn = pluggedIn;
+#endif
+}
+
 void AudioClient::outputNotify() {
     int recentUnfulfilled = _audioOutputIODevice.getRecentUnfulfilledReads();
     if (recentUnfulfilled > 0) {
diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
index fa7ac40a16..b1ccb496b6 100644
--- a/libraries/audio-client/src/AudioClient.h
+++ b/libraries/audio-client/src/AudioClient.h
@@ -68,8 +68,7 @@
 #define VOICE_RECOGNITION "voicerecognition"
 #define VOICE_COMMUNICATION "voicecommunication"
 
-#define ANDROID_SETTINGS_GROUP "Android"
-#define SETTING_AEC_KEY "aec"
+#define SETTING_AEC_KEY "Android/aec"
 #endif
 
 class QAudioInput;
@@ -176,6 +175,10 @@ public:
     static QString getWinDeviceName(wchar_t* guid);
 #endif
 
+#if defined(Q_OS_ANDROID)
+    bool isHeadsetPluggedIn() { return _isHeadsetPluggedIn; }
+#endif
+
 public slots:
     void start();
     void stop();
@@ -224,6 +227,9 @@ public slots:
     bool switchAudioDevice(QAudio::Mode mode, const QAudioDeviceInfo& deviceInfo = QAudioDeviceInfo());
     bool switchAudioDevice(QAudio::Mode mode, const QString& deviceName);
 
+    // Qt opensles plugin is not able to detect when the headset is plugged in
+    void setHeadsetPluggedIn(bool pluggedIn);
+
     float getInputVolume() const { return (_audioInput) ? (float)_audioInput->volume() : 0.0f; }
     void setInputVolume(float volume, bool emitSignal = true);
     void setReverb(bool reverb);
@@ -285,6 +291,7 @@ private:
 #ifdef Q_OS_ANDROID
     QTimer _checkInputTimer;
     long _inputReadsSinceLastCheck = 0l;
+    bool _isHeadsetPluggedIn;
 #endif
 
     class Gate {

From 27c7592d986af1424c4e85a7df5d44e2a50b2d7f Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Thu, 13 Sep 2018 14:30:42 -0300
Subject: [PATCH 03/56] Patch qt 5.11.1 to support voice_communication opensl
 es preset

---
 android/build.gradle | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/android/build.gradle b/android/build.gradle
index a6de0d469c..a5d40b9b43 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -73,16 +73,16 @@ def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/'
 def breakpadDumpSymsDir = new File("${appDir}/build/tmp/breakpadDumpSyms")
 
 def qtFile='qt-5.11.1_linux_armv8-libcpp_openssl.tgz'
-def qtChecksum='f312c47cd8b8dbca824c32af4eec5e66'
-def qtVersionId='nyCGcb91S4QbYeJhUkawO5x1lrLdSNB_'
+def qtChecksum='bf9e734d9c4e77c4807a38c93515e25c'
+def qtVersionId='Qt3rvI0O7l5gLkacia2vA6KAchTgFkFf'
 if (Os.isFamily(Os.FAMILY_MAC)) {
-    qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl.tgz'
-    qtChecksum='a0c8b394aec5b0fcd46714ca3a53278a'
-    qtVersionId='QNa.lwNJaPc0eGuIL.xZ8ebeTuLL7rh8'
+    qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz'
+    qtChecksum='5f12fd4fb25efe648c2fd161fd44998a'
+    qtVersionId='XXJ7ii1.xYzgFdWPnU.8mXnCj.q6y0Q5'
 } else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
-    qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl.tgz'
-    qtChecksum='d80aed4233ce9e222aae8376e7a94bf9'
-    qtVersionId='iDVXu0i3WEXRFIxQCtzcJ2XuKrE8RIqB'
+    qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz'
+    qtChecksum='0582191cc55431aa4f660848a542883e'
+    qtVersionId='JfWM0P_Mz5Qp0LwpzhrsRwN3fqlLSFeT'
 }
 
 def packages = [

From ec2c0226a5aadcf0ab762d463b10a30bc10c05ca Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Thu, 13 Sep 2018 16:42:12 -0300
Subject: [PATCH 04/56] Replace qt build version (osx)

---
 android/build.gradle | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/android/build.gradle b/android/build.gradle
index a5d40b9b43..8a3f4d75b9 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -77,8 +77,8 @@ def qtChecksum='bf9e734d9c4e77c4807a38c93515e25c'
 def qtVersionId='Qt3rvI0O7l5gLkacia2vA6KAchTgFkFf'
 if (Os.isFamily(Os.FAMILY_MAC)) {
     qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz'
-    qtChecksum='5f12fd4fb25efe648c2fd161fd44998a'
-    qtVersionId='XXJ7ii1.xYzgFdWPnU.8mXnCj.q6y0Q5'
+    qtChecksum='c83cc477c08a892e00c71764dca051a0'
+    qtVersionId='QNa.lwNJaPc0eGuIL.xZ8ebeTuLL7rh8'
 } else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
     qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz'
     qtChecksum='0582191cc55431aa4f660848a542883e'

From 74ea1548a929f4936684cc1d9ab59adfc553a85a Mon Sep 17 00:00:00 2001
From: Simon Walton <simon@highfidelity.io>
Date: Thu, 13 Sep 2018 14:24:49 -0700
Subject: [PATCH 05/56] Use chrono::system_clock for usecTimestampNow() on all
 platforms

---
 libraries/shared/src/SharedUtil.cpp | 81 ++---------------------------
 1 file changed, 5 insertions(+), 76 deletions(-)

diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp
index bb22a1e753..886445824b 100644
--- a/libraries/shared/src/SharedUtil.cpp
+++ b/libraries/shared/src/SharedUtil.cpp
@@ -19,8 +19,8 @@
 #include <time.h>
 #include <mutex>
 #include <thread>
-#include <set>
 #include <unordered_map>
+#include <chrono>
 
 #include <glm/glm.hpp>
 
@@ -122,87 +122,16 @@ void setGlobalInstance(const char* propertyName, const QVariant& variant) {
     }
 }
 
+// Do we still need this?
 static qint64 usecTimestampNowAdjust = 0; // in usec
 void usecTimestampNowForceClockSkew(qint64 clockSkew) {
     ::usecTimestampNowAdjust = clockSkew;
 }
 
-static std::atomic<qint64> TIME_REFERENCE { 0 }; // in usec
-static std::once_flag usecTimestampNowIsInitialized;
-static QElapsedTimer timestampTimer;
-
 quint64 usecTimestampNow(bool wantDebug) {
-    std::call_once(usecTimestampNowIsInitialized, [&] {
-        TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec
-        timestampTimer.start();
-    });
-    
-    quint64 now;
-    quint64 nsecsElapsed = timestampTimer.nsecsElapsed();
-    quint64 usecsElapsed = nsecsElapsed / NSECS_PER_USEC;  // nsec to usec
-    
-    // QElapsedTimer may not advance if the CPU has gone to sleep. In which case it
-    // will begin to deviate from real time. We detect that here, and reset if necessary
-    quint64 msecsCurrentTime = QDateTime::currentMSecsSinceEpoch();
-    quint64 msecsEstimate = (TIME_REFERENCE + usecsElapsed) / USECS_PER_MSEC; // usecs to msecs
-    int possibleSkew = msecsEstimate - msecsCurrentTime;
-    const int TOLERANCE = 10 * MSECS_PER_SECOND; // up to 10 seconds of skew is tolerated
-    if (abs(possibleSkew) > TOLERANCE) {
-        // reset our TIME_REFERENCE and timer
-        TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec
-        timestampTimer.restart();
-        now = TIME_REFERENCE + ::usecTimestampNowAdjust;
-
-        if (wantDebug) {
-            qCDebug(shared) << "usecTimestampNow() - resetting QElapsedTimer. ";
-            qCDebug(shared) << "    msecsCurrentTime:" << msecsCurrentTime;
-            qCDebug(shared) << "       msecsEstimate:" << msecsEstimate;
-            qCDebug(shared) << "        possibleSkew:" << possibleSkew;
-            qCDebug(shared) << "           TOLERANCE:" << TOLERANCE;
-            
-            qCDebug(shared) << "        nsecsElapsed:" << nsecsElapsed;
-            qCDebug(shared) << "        usecsElapsed:" << usecsElapsed;
-
-            QDateTime currentLocalTime = QDateTime::currentDateTime();
-
-            quint64 msecsNow = now / 1000; // usecs to msecs
-            QDateTime nowAsString;
-            nowAsString.setMSecsSinceEpoch(msecsNow);
-
-            qCDebug(shared) << "                 now:" << now;
-            qCDebug(shared) << "            msecsNow:" << msecsNow;
-
-            qCDebug(shared) << "         nowAsString:" << nowAsString.toString("yyyy-MM-dd hh:mm:ss.zzz");
-            qCDebug(shared) << "    currentLocalTime:" << currentLocalTime.toString("yyyy-MM-dd hh:mm:ss.zzz");
-        }
-    } else {
-        now = TIME_REFERENCE + usecsElapsed + ::usecTimestampNowAdjust;
-    }
-
-    if (wantDebug) {
-        QDateTime currentLocalTime = QDateTime::currentDateTime();
-
-        quint64 msecsNow = now / 1000; // usecs to msecs
-        QDateTime nowAsString;
-        nowAsString.setMSecsSinceEpoch(msecsNow);
-
-        quint64 msecsTimeReference = TIME_REFERENCE / 1000; // usecs to msecs
-        QDateTime timeReferenceAsString;
-        timeReferenceAsString.setMSecsSinceEpoch(msecsTimeReference);
-
-        qCDebug(shared) << "usecTimestampNow() - details... ";
-        qCDebug(shared) << "           TIME_REFERENCE:" << TIME_REFERENCE;
-        qCDebug(shared) << "    timeReferenceAsString:" << timeReferenceAsString.toString("yyyy-MM-dd hh:mm:ss.zzz");
-        qCDebug(shared) << "   usecTimestampNowAdjust:" << usecTimestampNowAdjust;
-        qCDebug(shared) << "             nsecsElapsed:" << nsecsElapsed;
-        qCDebug(shared) << "             usecsElapsed:" << usecsElapsed;
-        qCDebug(shared) << "                      now:" << now;
-        qCDebug(shared) << "                 msecsNow:" << msecsNow;
-        qCDebug(shared) << "              nowAsString:" << nowAsString.toString("yyyy-MM-dd hh:mm:ss.zzz");
-        qCDebug(shared) << "         currentLocalTime:" << currentLocalTime.toString("yyyy-MM-dd hh:mm:ss.zzz");
-    }
-    
-    return now;
+    using namespace std::chrono;
+    static const auto unixEpoch = system_clock::from_time_t(0);
+    return duration_cast<microseconds>(system_clock::now() - unixEpoch).count() + usecTimestampNowAdjust;
 }
 
 float secTimestampNow() {

From 1215ddbe88a6669173c7a635be0f0b8514e36fce Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Thu, 13 Sep 2018 18:50:46 -0300
Subject: [PATCH 06/56] Remove debug files from linux qt build. Remove extra
 space

---
 android/build.gradle                       | 6 +++---
 libraries/audio-client/src/AudioClient.cpp | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/android/build.gradle b/android/build.gradle
index 8a3f4d75b9..14f0779e29 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -72,9 +72,9 @@ def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a')
 def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/'
 def breakpadDumpSymsDir = new File("${appDir}/build/tmp/breakpadDumpSyms")
 
-def qtFile='qt-5.11.1_linux_armv8-libcpp_openssl.tgz'
-def qtChecksum='bf9e734d9c4e77c4807a38c93515e25c'
-def qtVersionId='Qt3rvI0O7l5gLkacia2vA6KAchTgFkFf'
+def qtFile='qt-5.11.1_linux_armv8-libcpp_openssl_patched.tgz'
+def qtChecksum='aa449d4bfa963f3bc9a9dfe558ba29df'
+def qtVersionId='3S97HBM5G5Xw9EfE52sikmgdN3t6C2MN'
 if (Os.isFamily(Os.FAMILY_MAC)) {
     qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz'
     qtChecksum='c83cc477c08a892e00c71764dca051a0'
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index f0ba0307cc..814cbf87e4 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -454,7 +454,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
         Setting::Handle<bool> enableAEC(SETTING_AEC_KEY, false);
         bool aecEnabled = enableAEC.get();
         auto audioClient = DependencyManager::get<AudioClient>();
-        bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false ;
+        bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false;
         auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
         for (auto inputDevice : inputDevices) {
             if (((headsetOn || !aecEnabled) && inputDevice.deviceName() == VOICE_RECOGNITION) ||

From 5795ef3c6323c9dbfd5782b486d3687d3016e322 Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Thu, 13 Sep 2018 19:43:28 -0300
Subject: [PATCH 07/56] Fix qt build (osx) file version

---
 android/build.gradle | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/android/build.gradle b/android/build.gradle
index 14f0779e29..aa7aa399b2 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -78,7 +78,7 @@ def qtVersionId='3S97HBM5G5Xw9EfE52sikmgdN3t6C2MN'
 if (Os.isFamily(Os.FAMILY_MAC)) {
     qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz'
     qtChecksum='c83cc477c08a892e00c71764dca051a0'
-    qtVersionId='QNa.lwNJaPc0eGuIL.xZ8ebeTuLL7rh8'
+    qtVersionId='OxBD7iKINv1HbyOXmAmDrBb8AF3N.Kup'
 } else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
     qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz'
     qtChecksum='0582191cc55431aa4f660848a542883e'

From 727aea20251af8bbe3c2b77d4ec35b9feda02662 Mon Sep 17 00:00:00 2001
From: Simon Walton <simon@highfidelity.io>
Date: Thu, 13 Sep 2018 18:21:16 -0700
Subject: [PATCH 08/56] Remove comment

---
 libraries/shared/src/SharedUtil.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp
index 886445824b..012e7aa1f5 100644
--- a/libraries/shared/src/SharedUtil.cpp
+++ b/libraries/shared/src/SharedUtil.cpp
@@ -122,7 +122,6 @@ void setGlobalInstance(const char* propertyName, const QVariant& variant) {
     }
 }
 
-// Do we still need this?
 static qint64 usecTimestampNowAdjust = 0; // in usec
 void usecTimestampNowForceClockSkew(qint64 clockSkew) {
     ::usecTimestampNowAdjust = clockSkew;

From b0cde2afcc40c94e10ef1b683af320fec6994947 Mon Sep 17 00:00:00 2001
From: Anthony Thibault <tony@highfidelity.io>
Date: Fri, 14 Sep 2018 15:05:17 -0700
Subject: [PATCH 09/56] Small addition to Anim Stats

* During a state machine interp the previous state now shows up in AlphaValues panel, with parentheses around the name.
* Added 3 new fields
  * Position - in world coordinate frame.
  * Heading - the facing angle in the world corrdinate frame.
  * Local Vel - the velocity of the character in the local coordinate frame. (left, forward, up).
---
 interface/resources/qml/AnimStats.qml        | 11 ++++++++--
 interface/src/ui/AnimStats.cpp               | 23 ++++++++++++++++++++
 interface/src/ui/AnimStats.h                 | 20 ++++++++++++++---
 libraries/animation/src/AnimStateMachine.cpp |  4 ++++
 libraries/animation/src/AnimVariant.cpp      |  9 ++++++++
 5 files changed, 62 insertions(+), 5 deletions(-)

diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml
index 35ed3799a6..d000f3d9be 100644
--- a/interface/resources/qml/AnimStats.qml
+++ b/interface/resources/qml/AnimStats.qml
@@ -47,6 +47,9 @@ Item {
                     id: firstCol
                     spacing: 4; x: 4; y: 4;
 
+                    StatText {
+                        text: root.positionText
+                    }
                     StatText {
                         text: "State Machines:---------------------------------------------------------------------------"
                     }
@@ -73,10 +76,12 @@ Item {
                     id: secondCol
                     spacing: 4; x: 4; y: 4;
 
+                    StatText {
+                        text: root.rotationText
+                    }
                     StatText {
                         text: "Anim Vars:--------------------------------------------------------------------------------"
                     }
-
                     ListView {
                         width: secondCol.width
                         height: root.animVars.length * 15
@@ -113,10 +118,12 @@ Item {
                     id: thirdCol
                     spacing: 4; x: 4; y: 4;
 
+                    StatText {
+                        text: root.velocityText
+                    }
                     StatText {
                         text: "Alpha Values:--------------------------------------------------------------------------"
                     }
-
                     ListView {
                         width: thirdCol.width
                         height: root.animAlphaValues.length * 15
diff --git a/interface/src/ui/AnimStats.cpp b/interface/src/ui/AnimStats.cpp
index 7b4778e365..e3579fa2dc 100644
--- a/interface/src/ui/AnimStats.cpp
+++ b/interface/src/ui/AnimStats.cpp
@@ -42,6 +42,29 @@ void AnimStats::updateStats(bool force) {
     auto myAvatar = avatarManager->getMyAvatar();
     auto debugAlphaMap = myAvatar->getSkeletonModel()->getRig().getDebugAlphaMap();
 
+    glm::vec3 position = myAvatar->getWorldPosition();
+    glm::quat rotation = myAvatar->getWorldOrientation();
+    glm::vec3 velocity = myAvatar->getWorldVelocity();
+
+    _positionText = QString("Position: (%1, %2, %3)").
+        arg(QString::number(position.x, 'f', 2)).
+        arg(QString::number(position.y, 'f', 2)).
+        arg(QString::number(position.z, 'f', 2));
+    emit positionTextChanged();
+
+    glm::vec3 eulerRotation = safeEulerAngles(rotation);
+    _rotationText = QString("Heading: %1").
+        arg(QString::number(glm::degrees(eulerRotation.y), 'f', 2));
+    emit rotationTextChanged();
+
+    // transform velocity into rig coordinate frame. z forward.
+    glm::vec3 localVelocity = Quaternions::Y_180 * glm::inverse(rotation) * velocity;
+    _velocityText = QString("Local Vel: (%1, %2, %3)").
+        arg(QString::number(localVelocity.x, 'f', 2)).
+        arg(QString::number(localVelocity.y, 'f', 2)).
+        arg(QString::number(localVelocity.z, 'f', 2));
+    emit velocityTextChanged();
+
     // update animation debug alpha values
     QStringList newAnimAlphaValues;
     qint64 now = usecTimestampNow();
diff --git a/interface/src/ui/AnimStats.h b/interface/src/ui/AnimStats.h
index 1a6795b498..7b6aaf7b54 100644
--- a/interface/src/ui/AnimStats.h
+++ b/interface/src/ui/AnimStats.h
@@ -19,6 +19,9 @@ class AnimStats : public QQuickItem {
     Q_PROPERTY(QStringList animAlphaValues READ animAlphaValues NOTIFY animAlphaValuesChanged)
     Q_PROPERTY(QStringList animVars READ animVars NOTIFY animVarsChanged)
     Q_PROPERTY(QStringList animStateMachines READ animStateMachines NOTIFY animStateMachinesChanged)
+    Q_PROPERTY(QString positionText READ positionText NOTIFY positionTextChanged)
+    Q_PROPERTY(QString rotationText READ rotationText NOTIFY rotationTextChanged)
+    Q_PROPERTY(QString velocityText READ velocityText NOTIFY velocityTextChanged)
 
 public:
     static AnimStats* getInstance();
@@ -27,9 +30,13 @@ public:
 
     void updateStats(bool force = false);
 
-    QStringList animAlphaValues() { return _animAlphaValues; }
-    QStringList animVars() { return _animVarsList; }
-    QStringList animStateMachines() { return _animStateMachines; }
+    QStringList animAlphaValues() const { return _animAlphaValues; }
+    QStringList animVars() const { return _animVarsList; }
+    QStringList animStateMachines() const { return _animStateMachines; }
+
+    QString positionText() const { return _positionText; }
+    QString rotationText() const { return _rotationText; }
+    QString velocityText() const { return _velocityText; }
 
 public slots:
     void forceUpdateStats() { updateStats(true); }
@@ -39,6 +46,9 @@ signals:
     void animAlphaValuesChanged();
     void animVarsChanged();
     void animStateMachinesChanged();
+    void positionTextChanged();
+    void rotationTextChanged();
+    void velocityTextChanged();
 
 private:
     QStringList _animAlphaValues;
@@ -50,6 +60,10 @@ private:
     std::map<QString, qint64> _animVarChangedTimers; // last time animVar value has changed.
 
     QStringList _animStateMachines;
+
+    QString _positionText;
+    QString _rotationText;
+    QString _velocityText;
 };
 
 #endif // hifi_AnimStats_h
diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp
index 7f46cd614a..fb13b8e71c 100644
--- a/libraries/animation/src/AnimStateMachine.cpp
+++ b/libraries/animation/src/AnimStateMachine.cpp
@@ -88,6 +88,10 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co
     processOutputJoints(triggersOut);
 
     context.addStateMachineInfo(_id, _currentState->getID(), _previousState->getID(), _duringInterp, _alpha);
+    if (_duringInterp) {
+        // hack: add previoius state to debug alpha map, with parens around it's name.
+        context.setDebugAlpha(QString("(%1)").arg(_previousState->getID()), 1.0f - _alpha, AnimNodeType::Clip);
+    }
 
     return _poses;
 }
diff --git a/libraries/animation/src/AnimVariant.cpp b/libraries/animation/src/AnimVariant.cpp
index 509462984a..21fe234f7b 100644
--- a/libraries/animation/src/AnimVariant.cpp
+++ b/libraries/animation/src/AnimVariant.cpp
@@ -140,14 +140,19 @@ std::map<QString, QString> AnimVariantMap::toDebugMap() const {
             result[pair.first] = QString::number(pair.second.getFloat(), 'f', 3);
             break;
         case AnimVariant::Type::Vec3: {
+            // To prevent filling up debug stats, don't show vec3 values
+            /*
             glm::vec3 value = pair.second.getVec3();
             result[pair.first] = QString("(%1, %2, %3)").
                 arg(QString::number(value.x, 'f', 3)).
                 arg(QString::number(value.y, 'f', 3)).
                 arg(QString::number(value.z, 'f', 3));
+            */
             break;
         }
         case AnimVariant::Type::Quat: {
+            // To prevent filling up the anim stats, don't show quat values
+            /*
             glm::quat value = pair.second.getQuat();
             result[pair.first] = QString("(%1, %2, %3, %4)").
                 arg(QString::number(value.x, 'f', 3)).
@@ -155,10 +160,14 @@ std::map<QString, QString> AnimVariantMap::toDebugMap() const {
                 arg(QString::number(value.z, 'f', 3)).
                 arg(QString::number(value.w, 'f', 3));
             break;
+            */
         }
         case AnimVariant::Type::String:
+            // To prevent filling up anim stats, don't show string values
+            /*
             result[pair.first] = pair.second.getString();
             break;
+            */
         default:
             assert(("invalid AnimVariant::Type", false));
         }

From 1a44054c494af587aa617e93eeb06d7e2e3bb343 Mon Sep 17 00:00:00 2001
From: Seth Alves <seth.alves@gmail.com>
Date: Sat, 15 Sep 2018 09:15:43 -0700
Subject: [PATCH 10/56] attempt fix for fb-17131

---
 libraries/entities/src/EntityScriptingInterface.cpp | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp
index d9924cb9fd..8f0fde5c9a 100644
--- a/libraries/entities/src/EntityScriptingInterface.cpp
+++ b/libraries/entities/src/EntityScriptingInterface.cpp
@@ -132,8 +132,8 @@ EntityItemProperties convertPropertiesToScriptSemantics(const EntityItemProperti
     EntityItemProperties scriptSideProperties = entitySideProperties;
     scriptSideProperties.setLocalPosition(entitySideProperties.getPosition());
     scriptSideProperties.setLocalRotation(entitySideProperties.getRotation());
-    scriptSideProperties.setLocalVelocity(entitySideProperties.getLocalVelocity());
-    scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getLocalAngularVelocity());
+    scriptSideProperties.setLocalVelocity(entitySideProperties.getVelocity());
+    scriptSideProperties.setLocalAngularVelocity(entitySideProperties.getAngularVelocity());
     scriptSideProperties.setLocalDimensions(entitySideProperties.getDimensions());
 
     bool success;
@@ -181,8 +181,6 @@ EntityItemProperties convertPropertiesFromScriptSemantics(const EntityItemProper
     EntityItemProperties entitySideProperties = scriptSideProperties;
     bool success;
 
-    // TODO -- handle velocity and angularVelocity
-
     if (scriptSideProperties.localPositionChanged()) {
         entitySideProperties.setPosition(scriptSideProperties.getLocalPosition());
     } else if (scriptSideProperties.positionChanged()) {

From f86075b2dda4303f706b65ebc9fb5f467615c302 Mon Sep 17 00:00:00 2001
From: Seth Alves <seth.alves@gmail.com>
Date: Sat, 15 Sep 2018 09:21:12 -0700
Subject: [PATCH 11/56] Revert "Fix for angularVelocity zeroing out on
 duplication or undo"

This reverts commit 9c96a10f6cf0c60eb39cab3ea3e7460d0dcda0a5.
---
 scripts/system/libraries/entitySelectionTool.js | 15 ++-------------
 1 file changed, 2 insertions(+), 13 deletions(-)

diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js
index 5bca58b1ac..1fc0f0611c 100644
--- a/scripts/system/libraries/entitySelectionTool.js
+++ b/scripts/system/libraries/entitySelectionTool.js
@@ -29,17 +29,6 @@ Script.include([
 SelectionManager = (function() {
     var that = {};
 
-    /**
-     * @description Removes known to be broken properties from a properties object
-     * @param properties
-     * @return properties
-     */
-    var fixRemoveBrokenProperties = function (properties) {
-        // Reason: Entity property is always set to 0,0,0 which causes it to override angularVelocity (see MS17131)
-        delete properties.localAngularVelocity;
-        return properties;
-    }
-
     // FUNCTION: SUBSCRIBE TO UPDATE MESSAGES
     function subscribeToUpdateMessages() {
         Messages.subscribe("entityToolUpdates");
@@ -130,7 +119,7 @@ SelectionManager = (function() {
         that.savedProperties = {};
         for (var i = 0; i < that.selections.length; i++) {
             var entityID = that.selections[i];
-            that.savedProperties[entityID] = fixRemoveBrokenProperties(Entities.getEntityProperties(entityID));
+            that.savedProperties[entityID] = Entities.getEntityProperties(entityID);
         }
     };
 
@@ -258,7 +247,7 @@ SelectionManager = (function() {
             var originalEntityID = entitiesToDuplicate[i];
             var properties = that.savedProperties[originalEntityID];
             if (properties === undefined) {
-                properties = fixRemoveBrokenProperties(Entities.getEntityProperties(originalEntityID));
+                properties = Entities.getEntityProperties(originalEntityID);
             }
             if (!properties.locked && (!properties.clientOnly || properties.owningAvatarID === MyAvatar.sessionUUID)) {
                 if (nonDynamicEntityIsBeingGrabbedByAvatar(properties)) {

From 11fe657c0588943a4d275f66923cf78c6237fbd2 Mon Sep 17 00:00:00 2001
From: Anthony Thibault <tony@highfidelity.io>
Date: Mon, 17 Sep 2018 10:33:04 -0700
Subject: [PATCH 12/56] Swaped AnimVar and StatMachine panels

---
 interface/resources/qml/AnimStats.qml | 59 ++++++++++++++-------------
 1 file changed, 30 insertions(+), 29 deletions(-)

diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml
index d000f3d9be..91ed0eb69b 100644
--- a/interface/resources/qml/AnimStats.qml
+++ b/interface/resources/qml/AnimStats.qml
@@ -50,35 +50,6 @@ Item {
                     StatText {
                         text: root.positionText
                     }
-                    StatText {
-                        text: "State Machines:---------------------------------------------------------------------------"
-                    }
-                    ListView {
-                        width: firstCol.width
-                        height: root.animStateMachines.length * 15
-                        visible: root.animStateMchines.length > 0;
-                        model: root.animStateMachines
-                        delegate: StatText {
-                            text: {
-                                return modelData;
-                            }
-                        }
-                    }
-                }
-            }
-
-            Rectangle {
-                width: secondCol.width + 8
-                height: secondCol.height + 8
-                color: root.bgColor;
-
-                Column {
-                    id: secondCol
-                    spacing: 4; x: 4; y: 4;
-
-                    StatText {
-                        text: root.rotationText
-                    }
                     StatText {
                         text: "Anim Vars:--------------------------------------------------------------------------------"
                     }
@@ -109,6 +80,36 @@ Item {
                 }
             }
 
+            Rectangle {
+                width: secondCol.width + 8
+                height: secondCol.height + 8
+                color: root.bgColor;
+
+                Column {
+                    id: secondCol
+                    spacing: 4; x: 4; y: 4;
+
+                    StatText {
+                        text: root.rotationText
+                    }
+                    StatText {
+                        text: "State Machines:---------------------------------------------------------------------------"
+                    }
+                    ListView {
+                        width: firstCol.width
+                        height: root.animStateMachines.length * 15
+                        visible: root.animStateMchines.length > 0;
+                        model: root.animStateMachines
+                        delegate: StatText {
+                            text: {
+                                return modelData;
+                            }
+                        }
+                    }
+
+                }
+            }
+
             Rectangle {
                 width: thirdCol.width + 8
                 height: thirdCol.height + 8

From 8229c14c4486c57e963f470dd9f0c54a56950d41 Mon Sep 17 00:00:00 2001
From: Anthony Thibault <ajt@highfidelity.io>
Date: Mon, 17 Sep 2018 15:17:43 -0700
Subject: [PATCH 13/56] Fix for type in AnimStats.qml

---
 interface/resources/qml/AnimStats.qml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml
index 91ed0eb69b..b1900cf0a7 100644
--- a/interface/resources/qml/AnimStats.qml
+++ b/interface/resources/qml/AnimStats.qml
@@ -98,7 +98,7 @@ Item {
                     ListView {
                         width: firstCol.width
                         height: root.animStateMachines.length * 15
-                        visible: root.animStateMchines.length > 0;
+                        visible: root.animStateMachines.length > 0;
                         model: root.animStateMachines
                         delegate: StatText {
                             text: {

From f2a046da9160edafec7b94d838f7ed19983a46ce Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Thu, 13 Sep 2018 12:15:18 -0700
Subject: [PATCH 14/56] Install marketplace item tester

---
 .../MarketplaceItemTester.qml                 | 157 ++++++++++++++++++
 interface/src/Application.cpp                 |   9 +-
 interface/src/commerce/QmlCommerce.cpp        |   1 +
 scripts/system/commerce/wallet.js             |  23 +++
 4 files changed, 186 insertions(+), 4 deletions(-)
 create mode 100644 interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
new file mode 100644
index 0000000000..80cf82678f
--- /dev/null
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -0,0 +1,157 @@
+//
+//  marketplaceItemTester
+//  qml/hifi/commerce/marketplaceItemTester
+//
+//  Load items not in the marketplace for testing purposes
+//
+//  Created by Zach Fox on 2018-09-05
+//  Copyright 2018 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+import QtQuick 2.5
+import QtQuick.Controls.Styles 1.4
+import QtQuick.Dialogs 1.0
+import QtQuick.Layouts 1.1
+import Hifi 1.0 as Hifi
+import "../../../styles-uit" as HifiStylesUit
+import "../../../controls-uit" as HifiControlsUit
+
+
+
+Rectangle {
+    id:root
+    HifiStylesUit.HifiConstants { id: hifi }
+    color: hifi.colors.white
+    ListModel { id: listModel }
+    ListView {
+        anchors.fill: parent
+        anchors.leftMargin: 12
+        anchors.bottomMargin: 40
+        model: listModel
+        spacing: 5
+        delegate: RowLayout {
+            anchors.left: parent.left
+            width: parent.width
+            spacing: 5
+            Text {
+                text: {
+                    var match = resource.match(/\/([^/]*)$/);
+                    return match ? match[1] : resource;
+                }
+                font.pointSize: 12
+                Layout.preferredWidth: root.width * .6
+                horizontalAlignment: Text.AlignBottom
+            }
+            Text {
+                text: assetType
+                font.pointSize: 10
+                Layout.preferredWidth: root.width * .2
+                horizontalAlignment: Text.AlignBottom
+            }
+            property var actions: {
+                "forward": function(resource, assetType){
+                    if ("application" == assetType) {
+                        Commerce.installApp(resource);
+                        Commerce.openApp(resource);
+                    }
+                    // XXX support other resource types here.
+                },
+                "trash": function(){
+                    if ("application" == assetType) {
+                        Commerce.uninstallApp(resource);
+                    }
+                    // XXX support other resource types here.
+                    listModel.remove(index);
+                }
+            }
+            Repeater {
+                model: [
+                    { "name": "forward", "glyph": hifi.glyphs.forward, "size": 30 },
+                    { "name": "trash", "glyph": hifi.glyphs.trash, "size": 22}
+                ]
+                HifiStylesUit.HiFiGlyphs {
+                    text: modelData.glyph
+                    size: modelData.size
+                    color: hifi.colors.black
+                    horizontalAlignment: Text.AlignHCenter
+                    MouseArea {
+                        anchors.fill: parent
+                        onClicked: {
+                            actions[modelData.name](resource, assetType);
+                        }
+                    }
+                }
+            }
+        }
+        headerPositioning: ListView.OverlayHeader
+        header: HifiStylesUit.RalewayRegular {
+            id: rootHeader
+            text: "Marketplace Item Tester"
+            height: 80
+            width: paintedWidth
+            size: 22
+            color: hifi.colors.black
+            anchors.left: parent.left
+            anchors.leftMargin: 12
+        }
+        footerPositioning: ListView.OverlayFooter
+        footer: Row {
+            id: rootActions
+            spacing: 20
+            anchors.horizontalCenter: parent.horizontalCenter
+            property string currentAction
+            function assetType(resource) {
+                return (resource.match(/\.app\.json$/) ? "application" :
+                        resource.match(/\.(?:fbx|fst)$/) ? "avatar" :
+                        resource.match(/\.json\.gz$/) ? "content set" :
+                        resource.match(/\.json$/) ? "entity or wearable" :
+                        "unknown")
+            }
+            function onResourceSelected(resource) {
+                // It is possible that we received the present signal
+                // from something other than our browserAsync window.
+                // Alas, there is nothing we can do about that so charge
+                // ahead as though we are sure the present signal is one
+                // we expect.
+                if ("load file" == currentAction) {
+                    print("disconnecting load file");
+                    Window.browseChanged.disconnect(onResourceSelected);
+                } else if ("load url" == currentAction) {
+                    print("disconnecting load url");
+                    Window.promptTextChanged.disconnect(onResourceSelected);
+                }
+                if (resource) {
+                    listModel.append( {
+                        "resource": resource.trim(),
+                        "assetType": assetType(resource.trim()) } );
+                }
+            }
+            property var actions: {
+                "Load File": function(){
+                    rootActions.currentAction = "load file";
+                    Window.browseChanged.connect(onResourceSelected);
+                    Window.browseAsync("Please select a file", "", "Assets (*.app.json *.json *.fbx *.json.gz)");
+                },
+                "Load URL": function(){
+                    rootActions.currentAction = "load url";
+                    Window.promptTextChanged.connect(onResourceSelected);
+                    Window.promptAsync("Please enter a URL", "");
+                }
+            }
+            Repeater {
+                model: [ "Load File", "Load URL" ]
+                HifiControlsUit.Button {
+                    color: hifi.buttons.blue
+                    fontSize: 20
+                    text: modelData
+                    width: root.width / 3
+                    height: 40
+                    onClicked: actions[text]()
+                }
+            }
+        }
+    }
+}
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 826f6a87a9..0415475397 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -1691,21 +1691,21 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
         return DependencyManager::get<OffscreenUi>()->navigationFocused() ? 1 : 0;
     });
     _applicationStateDevice->setInputVariant(STATE_PLATFORM_WINDOWS, []() -> float {
-#if defined(Q_OS_WIN) 
+#if defined(Q_OS_WIN)
         return 1;
 #else
         return 0;
 #endif
     });
     _applicationStateDevice->setInputVariant(STATE_PLATFORM_MAC, []() -> float {
-#if defined(Q_OS_MAC) 
+#if defined(Q_OS_MAC)
         return 1;
 #else
         return 0;
 #endif
     });
     _applicationStateDevice->setInputVariant(STATE_PLATFORM_ANDROID, []() -> float {
-#if defined(Q_OS_ANDROID) 
+#if defined(Q_OS_ANDROID)
         return 1;
 #else
         return 0;
@@ -2881,9 +2881,10 @@ void Application::initializeUi() {
         QUrl{ "hifi/commerce/common/CommerceLightbox.qml" },
         QUrl{ "hifi/commerce/common/EmulatedMarketplaceHeader.qml" },
         QUrl{ "hifi/commerce/common/FirstUseTutorial.qml" },
-        QUrl{ "hifi/commerce/common/SortableListModel.qml" },
         QUrl{ "hifi/commerce/common/sendAsset/SendAsset.qml" },
+        QUrl{ "hifi/commerce/common/SortableListModel.qml" },
         QUrl{ "hifi/commerce/inspectionCertificate/InspectionCertificate.qml" },
+        QUrl{ "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"},
         QUrl{ "hifi/commerce/purchases/PurchasedItem.qml" },
         QUrl{ "hifi/commerce/purchases/Purchases.qml" },
         QUrl{ "hifi/commerce/wallet/Help.qml" },
diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp
index 06da18148f..10be228310 100644
--- a/interface/src/commerce/QmlCommerce.cpp
+++ b/interface/src/commerce/QmlCommerce.cpp
@@ -228,6 +228,7 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) {
         // Thus, we protect against deleting the .app.json from the user's disk (below)
         // by skipping that check for the app we just installed.
         if ((justInstalledAppID != "") && ((justInstalledAppID + ".app.json") == appFileName)) {
+            installedAppsFromMarketplace += appFileName + ",";
             continue;
         }
 
diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js
index 5939b36438..12ec7dce6b 100644
--- a/scripts/system/commerce/wallet.js
+++ b/scripts/system/commerce/wallet.js
@@ -583,6 +583,27 @@
     //
     // Manage the connection between the button and the window.
     //
+    var DEVELOPER_MENU = "Developer";
+    var MARKETPLACE_ITEM_TESTER_LABEL = "Marktplace Item Tester";
+    var MARKETPLACE_ITEM_TESTER_QML_SOURCE = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml";
+    function installMarketplaceItemTester() {
+        if (!Menu.menuExists(DEVELOPER_MENU)) {
+           Menu.addMenu(DEVELOPER_MENU);
+        }
+
+        if (!Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)) {
+            Menu.addMenuItem({ menuName: DEVELOPER_MENU,
+                               menuItemName: MARKETPLACE_ITEM_TESTER_LABEL,
+                               isCheckable: false })
+        }
+
+        Menu.menuItemEvent.connect(function (menuItem) {
+            if (menuItem === MARKETPLACE_ITEM_TESTER_LABEL) {
+                tablet.loadQMLSource(MARKETPLACE_ITEM_TESTER_QML_SOURCE);
+            }
+        });
+    }
+
     var button;
     var buttonName = "WALLET";
     var tablet = null;
@@ -600,7 +621,9 @@
             button.clicked.connect(onButtonClicked);
             tablet.screenChanged.connect(onTabletScreenChanged);
         }
+        installMarketplaceItemTester();
     }
+
     var isWired = false;
     var isUpdateOverlaysWired = false;
     function off() {

From 8f5923d5624026d492ef5067dc915ccdfe56d4dc Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Tue, 18 Sep 2018 11:23:32 -0700
Subject: [PATCH 15/56] Show installed apps on start of marketplace item tester

Also, refactor a bit.
---
 .../MarketplaceItemTester.qml                 | 120 ++++++++++++------
 1 file changed, 79 insertions(+), 41 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index 80cf82678f..b36deb8b70 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -22,39 +22,61 @@ import "../../../controls-uit" as HifiControlsUit
 
 
 Rectangle {
-    id:root
+    id: root
+    property string installedApps
     HifiStylesUit.HifiConstants { id: hifi }
+    ListModel { id: resourceListModel }
+
     color: hifi.colors.white
-    ListModel { id: listModel }
+
+    function buildResourceObj(resource) {
+        resource = resource.trim();
+        var assetType = (resource.match(/\.app\.json$/) ? "application" :
+                         resource.match(/\.(?:fbx|fst)$/) ? "avatar" :
+                         resource.match(/\.json\.gz$/) ? "content set" :
+                         resource.match(/\.json$/) ? "entity or wearable" :
+                         "unknown");
+        return { "resource": resource, "assetType": assetType };
+    }
+
+    function installResourceObj(resourceObj) {
+        if ("application" == resourceObj["assetType"]) {
+            Commerce.installApp(resourceObj["resource"]);
+        }
+        // XXX support other asset types here
+    }
+
+    function addAllInstalledAppsToList() {
+        var i, apps = Commerce.getInstalledApps().split(","), len = apps.length;
+        for(i = 0; i < len - 1; ++i) {
+            if (i in apps) {
+                resourceListModel.append(buildResourceObj(apps[i]));
+            }
+        }
+    }
+
+    Component.onCompleted: {
+        // On startup, list includes all tester-installed assets.
+        addAllInstalledAppsToList();
+        // XXX support other asset types here
+    }
+
     ListView {
         anchors.fill: parent
         anchors.leftMargin: 12
         anchors.bottomMargin: 40
-        model: listModel
+        anchors.rightMargin: 12
+        model: resourceListModel
         spacing: 5
+
         delegate: RowLayout {
             anchors.left: parent.left
             width: parent.width
             spacing: 5
-            Text {
-                text: {
-                    var match = resource.match(/\/([^/]*)$/);
-                    return match ? match[1] : resource;
-                }
-                font.pointSize: 12
-                Layout.preferredWidth: root.width * .6
-                horizontalAlignment: Text.AlignBottom
-            }
-            Text {
-                text: assetType
-                font.pointSize: 10
-                Layout.preferredWidth: root.width * .2
-                horizontalAlignment: Text.AlignBottom
-            }
+
             property var actions: {
                 "forward": function(resource, assetType){
                     if ("application" == assetType) {
-                        Commerce.installApp(resource);
                         Commerce.openApp(resource);
                     }
                     // XXX support other resource types here.
@@ -64,9 +86,27 @@ Rectangle {
                         Commerce.uninstallApp(resource);
                     }
                     // XXX support other resource types here.
-                    listModel.remove(index);
+                    resourceListModel.remove(index);
                 }
             }
+
+            Text {
+                text: {
+                    var match = resource.match(/\/([^/]*)$/);
+                    return match ? match[1] : resource;
+                }
+                font.pointSize: 12
+                Layout.preferredWidth: root.width * .6
+                horizontalAlignment: Text.AlignBottom
+            }
+
+            Text {
+                text: assetType
+                font.pointSize: 10
+                Layout.preferredWidth: root.width * .2
+                horizontalAlignment: Text.AlignBottom
+            }
+
             Repeater {
                 model: [
                     { "name": "forward", "glyph": hifi.glyphs.forward, "size": 30 },
@@ -86,6 +126,7 @@ Rectangle {
                 }
             }
         }
+
         headerPositioning: ListView.OverlayHeader
         header: HifiStylesUit.RalewayRegular {
             id: rootHeader
@@ -97,19 +138,27 @@ Rectangle {
             anchors.left: parent.left
             anchors.leftMargin: 12
         }
+
         footerPositioning: ListView.OverlayFooter
         footer: Row {
             id: rootActions
             spacing: 20
             anchors.horizontalCenter: parent.horizontalCenter
+
             property string currentAction
-            function assetType(resource) {
-                return (resource.match(/\.app\.json$/) ? "application" :
-                        resource.match(/\.(?:fbx|fst)$/) ? "avatar" :
-                        resource.match(/\.json\.gz$/) ? "content set" :
-                        resource.match(/\.json$/) ? "entity or wearable" :
-                        "unknown")
+            property var actions: {
+                "Load File": function(){
+                    rootActions.currentAction = "load file";
+                    Window.browseChanged.connect(onResourceSelected);
+                    Window.browseAsync("Please select a file", "", "Assets (*.app.json *.json *.fbx *.json.gz)");
+                },
+                "Load URL": function(){
+                    rootActions.currentAction = "load url";
+                    Window.promptTextChanged.connect(onResourceSelected);
+                    Window.promptAsync("Please enter a URL", "");
+                }
             }
+
             function onResourceSelected(resource) {
                 // It is possible that we received the present signal
                 // from something other than our browserAsync window.
@@ -124,23 +173,12 @@ Rectangle {
                     Window.promptTextChanged.disconnect(onResourceSelected);
                 }
                 if (resource) {
-                    listModel.append( {
-                        "resource": resource.trim(),
-                        "assetType": assetType(resource.trim()) } );
-                }
-            }
-            property var actions: {
-                "Load File": function(){
-                    rootActions.currentAction = "load file";
-                    Window.browseChanged.connect(onResourceSelected);
-                    Window.browseAsync("Please select a file", "", "Assets (*.app.json *.json *.fbx *.json.gz)");
-                },
-                "Load URL": function(){
-                    rootActions.currentAction = "load url";
-                    Window.promptTextChanged.connect(onResourceSelected);
-                    Window.promptAsync("Please enter a URL", "");
+                    var resourceObj = buildResourceObj(resource);
+                    installResourceObj(resourceObj);
+                    resourceListModel.append(resourceObj);
                 }
             }
+
             Repeater {
                 model: [ "Load File", "Load URL" ]
                 HifiControlsUit.Button {

From 645ad2bb7a3493a73f5aace63a49b8d03b605759 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Tue, 18 Sep 2018 12:36:03 -0700
Subject: [PATCH 16/56] Enable marketplace item test for avatar url

---
 .../marketplaceItemTester/MarketplaceItemTester.qml       | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index b36deb8b70..fed8480239 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -76,8 +76,12 @@ Rectangle {
 
             property var actions: {
                 "forward": function(resource, assetType){
-                    if ("application" == assetType) {
-                        Commerce.openApp(resource);
+                    switch(assetType) {
+                        case "application":
+                            Commerce.openApp(resource);
+                            break
+                        case "avatar":
+                            MyAvatar.useFullAvatarURL(resource);
                     }
                     // XXX support other resource types here.
                 },

From a592565b7b0ee6c941cb6e6de561d0bbd1bbe160 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Tue, 18 Sep 2018 13:25:01 -0700
Subject: [PATCH 17/56] Enable marketplace item test for avatar file

---
 .../commerce/marketplaceItemTester/MarketplaceItemTester.qml  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index fed8480239..4ef6109752 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -32,7 +32,7 @@ Rectangle {
     function buildResourceObj(resource) {
         resource = resource.trim();
         var assetType = (resource.match(/\.app\.json$/) ? "application" :
-                         resource.match(/\.(?:fbx|fst)$/) ? "avatar" :
+                         resource.match(/\.fst$/) ? "avatar" :
                          resource.match(/\.json\.gz$/) ? "content set" :
                          resource.match(/\.json$/) ? "entity or wearable" :
                          "unknown");
@@ -154,7 +154,7 @@ Rectangle {
                 "Load File": function(){
                     rootActions.currentAction = "load file";
                     Window.browseChanged.connect(onResourceSelected);
-                    Window.browseAsync("Please select a file", "", "Assets (*.app.json *.json *.fbx *.json.gz)");
+                    Window.browseAsync("Please select a file (*.app.json *.json *.fst *.json.gz)", "", "Assets (*.app.json *.json *.fst *.json.gz)");
                 },
                 "Load URL": function(){
                     rootActions.currentAction = "load url";

From cfa9d185f02dfd764abb7cf2a7473ebaa097f5c0 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Tue, 18 Sep 2018 14:02:16 -0700
Subject: [PATCH 18/56] Add full resource paths to list in marketplace item
 tester

---
 .../MarketplaceItemTester.qml                 | 27 +++++++++++++------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index 4ef6109752..8b0ecf3436 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -94,21 +94,32 @@ Rectangle {
                 }
             }
 
-            Text {
-                text: {
-                    var match = resource.match(/\/([^/]*)$/);
-                    return match ? match[1] : resource;
-                }
-                font.pointSize: 12
+            Column {
                 Layout.preferredWidth: root.width * .6
-                horizontalAlignment: Text.AlignBottom
+                spacing: 5
+                Text {
+                    text: {
+                        var match = resource.match(/\/([^/]*)$/);
+                        return match ? match[1] : resource;
+                    }
+                    font.pointSize: 12
+                    horizontalAlignment: Text.AlignBottom
+                }
+                Text {
+                    text: resource
+                    font.pointSize: 8
+                    width: root.width * .6
+                    horizontalAlignment: Text.AlignBottom
+                    wrapMode: Text.WrapAnywhere
+                }
             }
 
             Text {
                 text: assetType
                 font.pointSize: 10
                 Layout.preferredWidth: root.width * .2
-                horizontalAlignment: Text.AlignBottom
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Test.AlignVCenter
             }
 
             Repeater {

From 3a7785d10fcd660876d81d9a0437d1821d756bef Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Tue, 18 Sep 2018 14:01:12 -0700
Subject: [PATCH 19/56] fix for deadlock in other avatar entity removal

---
 interface/src/avatar/AvatarManager.cpp  | 40 +++++++++-------
 interface/src/avatar/AvatarManager.h    |  5 +-
 libraries/avatars/src/AvatarHashMap.cpp | 62 ++++++++++++++++++-------
 libraries/avatars/src/AvatarHashMap.h   |  2 +
 4 files changed, 74 insertions(+), 35 deletions(-)

diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index 1faf17ea9a..d92a5c81da 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -456,25 +456,29 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
 }
 
 void AvatarManager::clearOtherAvatars() {
-    // Remove other avatars from the world but don't actually remove them from _avatarHash
-    // each will either be removed on timeout or will re-added to the world on receipt of update.
-    const render::ScenePointer& scene = qApp->getMain3DScene();
-    render::Transaction transaction;
-
-    QReadLocker locker(&_hashLock);
-    AvatarHash::iterator avatarIterator =  _avatarHash.begin();
-    while (avatarIterator != _avatarHash.end()) {
-        auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value());
-        if (avatar != _myAvatar) {
-            handleRemovedAvatar(avatar);
-            avatarIterator = _avatarHash.erase(avatarIterator);
-        } else {
-            ++avatarIterator;
-        }
-    }
-    assert(scene);
-    scene->enqueueTransaction(transaction);
     _myAvatar->clearLookAtTargetAvatar();
+
+    // setup a vector of removed avatars outside the scope of the hash lock
+    std::vector<AvatarSharedPointer> removedAvatars;
+
+    {
+        QWriteLocker locker(&_hashLock);
+
+        auto avatarIterator =  _avatarHash.begin();
+        while (avatarIterator != _avatarHash.end()) {
+            auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value());
+            if (avatar != _myAvatar) {
+                removedAvatars.push_back(avatar);
+                avatarIterator = _avatarHash.erase(avatarIterator);
+            } else {
+                ++avatarIterator;
+            }
+        }
+    }    
+
+    for (auto& av : removedAvatars) {
+        handleRemovedAvatar(av);
+    }
 }
 
 void AvatarManager::deleteAllAvatars() {
diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h
index 306ba6f39b..407b3c50de 100644
--- a/interface/src/avatar/AvatarManager.h
+++ b/interface/src/avatar/AvatarManager.h
@@ -204,7 +204,10 @@ private:
     void simulateAvatarFades(float deltaTime);
 
     AvatarSharedPointer newSharedAvatar() override;
-    void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
+    
+    // must not be called while holding the hash lock
+    void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar,
+                             KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
 
     QVector<AvatarSharedPointer> _avatarsToFade;
 
diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp
index d205a915f8..2ee51cca17 100644
--- a/libraries/avatars/src/AvatarHashMap.cpp
+++ b/libraries/avatars/src/AvatarHashMap.cpp
@@ -66,6 +66,22 @@ void AvatarReplicas::removeReplicas(const QUuid& parentID) {
     }
 }
 
+std::vector<AvatarSharedPointer> AvatarReplicas::takeReplicas(const QUuid& parentID) {
+    std::vector<AvatarSharedPointer> replicas;
+
+    auto it = _replicasMap.find(parentID);
+
+    if (it != _replicasMap.end()) {
+        // take a copy of the replica shared pointers for this parent
+        replicas = it->second;
+
+        // erase the replicas for this parent from our map
+        _replicasMap.erase(it);
+    }
+
+    return replicas;
+}
+
 void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) {
     if (_replicasMap.find(parentID) != _replicasMap.end()) {
         auto &replicas = _replicasMap[parentID];
@@ -386,24 +402,31 @@ void AvatarHashMap::processKillAvatar(QSharedPointer<ReceivedMessage> message, S
 }
 
 void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) {
-    QWriteLocker locker(&_hashLock);
+    std::vector<AvatarSharedPointer> removedAvatars;
 
-    auto replicaIDs = _replicas.getReplicaIDs(sessionUUID);
-    _replicas.removeReplicas(sessionUUID);
-    for (auto id : replicaIDs) {
-        auto removedReplica = _avatarHash.take(id);
-        if (removedReplica) {
-            handleRemovedAvatar(removedReplica, removalReason);
+    {
+        QWriteLocker locker(&_hashLock);
+
+        auto replicas = _replicas.takeReplicas(sessionUUID);
+
+        for (auto& replica : replicas) {
+            auto removedReplica = _avatarHash.take(replica->getID());
+            if (removedReplica) {
+                removedAvatars.push_back(removedReplica);
+            }
+        }
+
+        _pendingAvatars.remove(sessionUUID);
+        auto removedAvatar = _avatarHash.take(sessionUUID);
+
+        if (removedAvatar) {
+            removedAvatars.push_back(removedAvatar);
         }
     }
 
-    _pendingAvatars.remove(sessionUUID);
-    auto removedAvatar = _avatarHash.take(sessionUUID);
-
-    if (removedAvatar) {
+    for (auto& removedAvatar: removedAvatars) {
         handleRemovedAvatar(removedAvatar, removalReason);
     }
-
 }
 
 void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
@@ -421,11 +444,18 @@ void AvatarHashMap::sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& ol
 }
 
 void AvatarHashMap::clearOtherAvatars() {
-    QWriteLocker locker(&_hashLock);
+    QList<AvatarSharedPointer> removedAvatars;
 
-    for (auto& av : _avatarHash) {
-        handleRemovedAvatar(av);
+    {
+        QWriteLocker locker(&_hashLock);
+
+        // grab a copy of the current avatars so we can call handleRemoveAvatar for them
+        removedAvatars = _avatarHash.values();
+
+        _avatarHash.clear();
     }
 
-    _avatarHash.clear();
+    for (auto& av : removedAvatars) {
+        handleRemovedAvatar(av);
+    }
 }
diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h
index 70d7f8c04d..724dd1deac 100644
--- a/libraries/avatars/src/AvatarHashMap.h
+++ b/libraries/avatars/src/AvatarHashMap.h
@@ -49,6 +49,7 @@ public:
     void parseDataFromBuffer(const QUuid& parentID, const QByteArray& buffer);
     void processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged);
     void removeReplicas(const QUuid& parentID);
+    std::vector<AvatarSharedPointer> takeReplicas(const QUuid& parentID);
     void processTrait(const QUuid& parentID, AvatarTraits::TraitType traitType, QByteArray traitBinaryData);
     void processDeletedTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID);
     void processTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType,
@@ -180,6 +181,7 @@ protected:
     virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock
     virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason);
 
+    // must not be called while holding the hash lock
     virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason);
     
     AvatarHash _avatarHash;

From 795580f4e1076e2ba9800d54f82e621f2cc030b7 Mon Sep 17 00:00:00 2001
From: Wayne Chen <wayne@highfidelity.io>
Date: Wed, 19 Sep 2018 09:55:15 -0700
Subject: [PATCH 20/56] adding 404 redirect toggle with interstitial

---
 interface/src/ConnectionMonitor.cpp        | 28 ++++++++++++++++++----
 libraries/networking/src/AddressManager.h  | 21 ++++++----------
 libraries/networking/src/DomainHandler.cpp | 20 +++++++++++-----
 3 files changed, 45 insertions(+), 24 deletions(-)

diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp
index 3c85cfb339..5eacc5259f 100644
--- a/interface/src/ConnectionMonitor.cpp
+++ b/interface/src/ConnectionMonitor.cpp
@@ -18,11 +18,15 @@
 #include <DomainHandler.h>
 #include <AddressManager.h>
 #include <NodeList.h>
+#include <SettingHandle.h>
 
 // Because the connection monitor is created at startup, the time we wait on initial load
 // should be longer to allow the application to initialize.
 static const int ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 10000;
 static const int REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 5000;
+static const int ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 10000;
+static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000;
+Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
 
 void ConnectionMonitor::init() {
     // Connect to domain disconnected message
@@ -37,20 +41,36 @@ void ConnectionMonitor::init() {
 
     _timer.setSingleShot(true);
     if (!domainHandler.isConnected()) {
-        _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS);
+        if (enableInterstitialMode.get()) {
+            _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS);
+        } else {
+            _timer.start(ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS);
+        }
     }
 
     connect(&_timer, &QTimer::timeout, this, [this]() {
-        qDebug() << "ConnectionMonitor: Redirecting to 404 error domain";
         // set in a timeout error
-        emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5);
+        if (enableInterstitialMode.get()) {
+            qDebug() << "ConnectionMonitor: Redirecting to 404 error domain";
+            emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5);
+        } else {
+            qDebug() << "ConnectionMonitor: Showing connection failure window";
+            DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(true);
+        }
     });
 }
 
 void ConnectionMonitor::startTimer() {
-    _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS);
+    if (enableInterstitialMode.get()) {
+        _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS);
+    } else {
+        _timer.start(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS);
+    }
 }
 
 void ConnectionMonitor::stopTimer() {
     _timer.stop();
+    if (!enableInterstitialMode.get()) {
+        DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(false);
+    }
 }
diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h
index c7cdf8f4ea..17041a5fd7 100644
--- a/libraries/networking/src/AddressManager.h
+++ b/libraries/networking/src/AddressManager.h
@@ -140,8 +140,7 @@ public:
      * </table>
      * @typedef {number} location.LookupTrigger
      */
-    enum LookupTrigger
-    {
+    enum LookupTrigger {
         UserInput,
         Back,
         Forward,
@@ -207,9 +206,8 @@ public slots:
     // functions and signals that should be exposed are moved to a scripting interface class.
     //
     // we currently expect this to be called from NodeList once handleLookupString has been called with a path
-    bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) {
-        return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString);
-    }
+    bool goToViewpointForPath(const QString& viewpointString, const QString& pathString)
+        { return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); }
 
     /**jsdoc
      * Go back to the previous location in your navigation history, if there is one.
@@ -231,8 +229,7 @@ public slots:
      *     location history is correctly maintained.
      */
     void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) {
-        handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger);
-    }
+        handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); }
 
     /**jsdoc
      * Go to the default "welcome" metaverse address.
@@ -364,8 +361,7 @@ signals:
      * location.locationChangeRequired.connect(onLocationChangeRequired);
      */
     void locationChangeRequired(const glm::vec3& newPosition,
-                                bool hasOrientationChange,
-                                const glm::quat& newOrientation,
+                                bool hasOrientationChange, const glm::quat& newOrientation,
                                 bool shouldFaceLocation);
 
     /**jsdoc
@@ -448,11 +444,8 @@ private:
 
     bool handleNetworkAddress(const QString& lookupString, LookupTrigger trigger, bool& hostChanged);
     void handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly = false);
-    bool handleViewpoint(const QString& viewpointString,
-                         bool shouldFace,
-                         LookupTrigger trigger,
-                         bool definitelyPathOnly = false,
-                         const QString& pathString = QString());
+    bool handleViewpoint(const QString& viewpointString, bool shouldFace, LookupTrigger trigger,
+                         bool definitelyPathOnly = false, const QString& pathString = QString());
     bool handleUsername(const QString& lookupString);
     bool handleDomainID(const QString& host);
 
diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp
index f34a93de96..d02cb75e8b 100644
--- a/libraries/networking/src/DomainHandler.cpp
+++ b/libraries/networking/src/DomainHandler.cpp
@@ -14,6 +14,7 @@
 #include <math.h>
 
 #include <PathUtils.h>
+#include <SettingHandle.h>
 
 #include <QtCore/QJsonDocument>
 #include <QtCore/QDataStream>
@@ -29,6 +30,8 @@
 #include "UserActivityLogger.h"
 #include "NetworkLogging.h"
 
+Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
+
 DomainHandler::DomainHandler(QObject* parent) :
     QObject(parent),
     _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
@@ -485,14 +488,19 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
 #if defined(Q_OS_ANDROID)
         emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo);
 #else
-        if (reasonCode == ConnectionRefusedReason::ProtocolMismatch || reasonCode == ConnectionRefusedReason::NotAuthorized) {
-            // ingest the error - this is a "hard" connection refusal.
-            setRedirectErrorState(_errorDomainURL, (int)reasonCode);
-        } else {
+        if (enableInterstitialMode.get()) {
+            if (reasonCode == ConnectionRefusedReason::ProtocolMismatch || reasonCode == ConnectionRefusedReason::NotAuthorized) {
+                // ingest the error - this is a "hard" connection refusal.
+                setRedirectErrorState(_errorDomainURL, (int)reasonCode);
+            } else {
+                emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo);
+            }
+            _lastDomainConnectionError = (int)reasonCode;
+        }
+        else {
             emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo);
         }
-        _lastDomainConnectionError = (int)reasonCode;
-#endif
+       #endif
     }
 
     auto accountManager = DependencyManager::get<AccountManager>();

From 01594d7a52a07c4a7a2e5443fbdcca6b8328a18e Mon Sep 17 00:00:00 2001
From: Wayne Chen <wayne@highfidelity.io>
Date: Wed, 19 Sep 2018 10:17:14 -0700
Subject: [PATCH 21/56] fixing settings check in domain handler

---
 libraries/networking/src/DomainHandler.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp
index d02cb75e8b..d2d576d8b7 100644
--- a/libraries/networking/src/DomainHandler.cpp
+++ b/libraries/networking/src/DomainHandler.cpp
@@ -30,8 +30,6 @@
 #include "UserActivityLogger.h"
 #include "NetworkLogging.h"
 
-Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
-
 DomainHandler::DomainHandler(QObject* parent) :
     QObject(parent),
     _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
@@ -488,6 +486,8 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
 #if defined(Q_OS_ANDROID)
         emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo);
 #else
+        Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
+
         if (enableInterstitialMode.get()) {
             if (reasonCode == ConnectionRefusedReason::ProtocolMismatch || reasonCode == ConnectionRefusedReason::NotAuthorized) {
                 // ingest the error - this is a "hard" connection refusal.

From a43799fffa0199352221535f8237a0f1c18fc3dc Mon Sep 17 00:00:00 2001
From: Wayne Chen <wayne@highfidelity.io>
Date: Wed, 19 Sep 2018 10:40:49 -0700
Subject: [PATCH 22/56] moving settings check within slotted functions

---
 interface/src/ConnectionMonitor.cpp | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp
index 5eacc5259f..8f0c12634c 100644
--- a/interface/src/ConnectionMonitor.cpp
+++ b/interface/src/ConnectionMonitor.cpp
@@ -26,7 +26,6 @@ static const int ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 10000;
 static const int REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 5000;
 static const int ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 10000;
 static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000;
-Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
 
 void ConnectionMonitor::init() {
     // Connect to domain disconnected message
@@ -41,6 +40,7 @@ void ConnectionMonitor::init() {
 
     _timer.setSingleShot(true);
     if (!domainHandler.isConnected()) {
+        Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
         if (enableInterstitialMode.get()) {
             _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS);
         } else {
@@ -50,6 +50,7 @@ void ConnectionMonitor::init() {
 
     connect(&_timer, &QTimer::timeout, this, [this]() {
         // set in a timeout error
+        Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
         if (enableInterstitialMode.get()) {
             qDebug() << "ConnectionMonitor: Redirecting to 404 error domain";
             emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5);
@@ -61,6 +62,7 @@ void ConnectionMonitor::init() {
 }
 
 void ConnectionMonitor::startTimer() {
+    Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
     if (enableInterstitialMode.get()) {
         _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS);
     } else {
@@ -70,6 +72,7 @@ void ConnectionMonitor::startTimer() {
 
 void ConnectionMonitor::stopTimer() {
     _timer.stop();
+    Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
     if (!enableInterstitialMode.get()) {
         DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(false);
     }

From 7e796f7e7f268a8ab3fd49c4b1f3ef90e41c2447 Mon Sep 17 00:00:00 2001
From: Wayne Chen <wayne@highfidelity.io>
Date: Wed, 19 Sep 2018 14:26:30 -0700
Subject: [PATCH 23/56] committing requested changes

---
 interface/src/ConnectionMonitor.cpp        | 13 ++++---------
 interface/src/ConnectionMonitor.h          |  5 ++++-
 libraries/networking/src/DomainHandler.cpp |  7 ++-----
 libraries/networking/src/DomainHandler.h   |  3 +++
 4 files changed, 13 insertions(+), 15 deletions(-)

diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp
index 8f0c12634c..ca90d039f4 100644
--- a/interface/src/ConnectionMonitor.cpp
+++ b/interface/src/ConnectionMonitor.cpp
@@ -18,7 +18,6 @@
 #include <DomainHandler.h>
 #include <AddressManager.h>
 #include <NodeList.h>
-#include <SettingHandle.h>
 
 // Because the connection monitor is created at startup, the time we wait on initial load
 // should be longer to allow the application to initialize.
@@ -40,8 +39,7 @@ void ConnectionMonitor::init() {
 
     _timer.setSingleShot(true);
     if (!domainHandler.isConnected()) {
-        Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
-        if (enableInterstitialMode.get()) {
+        if (_enableInterstitialMode.get()) {
             _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS);
         } else {
             _timer.start(ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS);
@@ -50,8 +48,7 @@ void ConnectionMonitor::init() {
 
     connect(&_timer, &QTimer::timeout, this, [this]() {
         // set in a timeout error
-        Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
-        if (enableInterstitialMode.get()) {
+        if (_enableInterstitialMode.get()) {
             qDebug() << "ConnectionMonitor: Redirecting to 404 error domain";
             emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5);
         } else {
@@ -62,8 +59,7 @@ void ConnectionMonitor::init() {
 }
 
 void ConnectionMonitor::startTimer() {
-    Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
-    if (enableInterstitialMode.get()) {
+    if (_enableInterstitialMode.get()) {
         _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS);
     } else {
         _timer.start(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS);
@@ -72,8 +68,7 @@ void ConnectionMonitor::startTimer() {
 
 void ConnectionMonitor::stopTimer() {
     _timer.stop();
-    Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
-    if (!enableInterstitialMode.get()) {
+    if (!_enableInterstitialMode.get()) {
         DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(false);
     }
 }
diff --git a/interface/src/ConnectionMonitor.h b/interface/src/ConnectionMonitor.h
index 5e75e2618b..f0589a3b8c 100644
--- a/interface/src/ConnectionMonitor.h
+++ b/interface/src/ConnectionMonitor.h
@@ -15,6 +15,8 @@
 #include <QObject>
 #include <QTimer>
 
+#include <SettingHandle.h>
+
 class QUrl;
 class QString;
 
@@ -32,6 +34,7 @@ private slots:
 
 private:
     QTimer _timer;
+    Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false };
 };
 
-#endif // hifi_ConnectionMonitor_h
\ No newline at end of file
+#endif // hifi_ConnectionMonitor_h
diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp
index d2d576d8b7..b2f118c5c8 100644
--- a/libraries/networking/src/DomainHandler.cpp
+++ b/libraries/networking/src/DomainHandler.cpp
@@ -14,7 +14,6 @@
 #include <math.h>
 
 #include <PathUtils.h>
-#include <SettingHandle.h>
 
 #include <QtCore/QJsonDocument>
 #include <QtCore/QDataStream>
@@ -486,9 +485,8 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
 #if defined(Q_OS_ANDROID)
         emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo);
 #else
-        Setting::Handle<bool> enableInterstitialMode{ "enableInterstitialMode", false };
 
-        if (enableInterstitialMode.get()) {
+        if (_enableInterstitialMode.get()) {
             if (reasonCode == ConnectionRefusedReason::ProtocolMismatch || reasonCode == ConnectionRefusedReason::NotAuthorized) {
                 // ingest the error - this is a "hard" connection refusal.
                 setRedirectErrorState(_errorDomainURL, (int)reasonCode);
@@ -496,8 +494,7 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
                 emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo);
             }
             _lastDomainConnectionError = (int)reasonCode;
-        }
-        else {
+        } else {
             emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo);
         }
        #endif
diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h
index 3d361482ac..5fa920a554 100644
--- a/libraries/networking/src/DomainHandler.h
+++ b/libraries/networking/src/DomainHandler.h
@@ -19,6 +19,8 @@
 #include <QtCore/QUrl>
 #include <QtNetwork/QHostInfo>
 
+#include <SettingHandle.h>
+
 #include "HifiSockAddr.h"
 #include "NetworkPeer.h"
 #include "NLPacket.h"
@@ -221,6 +223,7 @@ private:
     NetworkPeer _icePeer;
     bool _isConnected { false };
     bool _isInErrorState { false };
+    Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false };
     QJsonObject _settingsObject;
     QString _pendingPath;
     QTimer _settingsTimer;

From 8805ce49e3e1349c4a123cca909c00aaf915e934 Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Wed, 19 Sep 2018 15:06:58 -0700
Subject: [PATCH 24/56] Remove verbose logging of keyboard messages in edit.js
 and gridTool.js

---
 scripts/system/edit.js               | 4 +---
 scripts/system/libraries/gridTool.js | 1 -
 2 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/scripts/system/edit.js b/scripts/system/edit.js
index 88ae2852be..b1f01e1ea9 100644
--- a/scripts/system/edit.js
+++ b/scripts/system/edit.js
@@ -2134,9 +2134,7 @@ var PropertiesTool = function (opts) {
     var onWebEventReceived = function(data) {
         try {
             data = JSON.parse(data);
-        }
-        catch(e) {
-            print('Edit.js received web event that was not valid json.');
+        } catch(e) {
             return;
         }
         var i, properties, dY, diff, newPosition;
diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js
index 3a114f23c7..6f62742e8f 100644
--- a/scripts/system/libraries/gridTool.js
+++ b/scripts/system/libraries/gridTool.js
@@ -267,7 +267,6 @@ GridTool = function(opts) {
         try {
             data = JSON.parse(data);
         } catch (e) {
-            print("gridTool.js: Error parsing JSON: " + e.name + " data " + data);
             return;
         }
 

From 5bc7d944f961fb34e3d5b2ce1e35dd2e952ad5ea Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Wed, 19 Sep 2018 15:17:13 -0700
Subject: [PATCH 25/56] Remove whitespace

---
 .../hifi/commerce/purchases/PurchasedItem.qml  | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
index 032d9b0199..eeb9ac3c54 100644
--- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
+++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml
@@ -59,7 +59,7 @@ Item {
 
     Connections {
         target: Commerce;
-        
+
         onContentSetChanged: {
             if (contentSetHref === root.itemHref) {
                 showConfirmation = true;
@@ -135,7 +135,7 @@ Item {
                 anchors.topMargin: 8;
                 width: 30;
                 height: width;
-            
+
                 HiFiGlyphs {
                     id: closeContextMenuGlyph;
                     text: hifi.glyphs.close;
@@ -376,7 +376,7 @@ Item {
                 }
             }
         }
-        
+
         transform: Rotation {
             id: rotation;
             origin.x: flipable.width/2;
@@ -509,7 +509,7 @@ Item {
                     }
                 verticalAlignment: Text.AlignTop;
             }
-        
+
             HiFiGlyphs {
                 id: statusIcon;
                 text: {
@@ -588,7 +588,7 @@ Item {
                 border.width: 1;
                 border.color: "#E2334D";
             }
-            
+
             HiFiGlyphs {
                 id: contextMenuGlyph;
                 text: hifi.glyphs.verticalEllipsis;
@@ -615,7 +615,7 @@ Item {
                 }
             }
         }
-        
+
         Rectangle {
             id: rezzedNotifContainer;
             z: 998;
@@ -663,13 +663,13 @@ Item {
                     Tablet.playSound(TabletEnums.ButtonHover);
                 }
             }
-    
+
             onFocusChanged: {
                 if (focus) {
                     Tablet.playSound(TabletEnums.ButtonHover);
                 }
             }
-            
+
             onClicked: {
                 Tablet.playSound(TabletEnums.ButtonClick);
                 if (root.itemType === "contentSet") {
@@ -775,7 +775,7 @@ Item {
             // Style
             color: hifi.colors.redAccent;
             horizontalAlignment: Text.AlignRight;
-            
+
             MouseArea {
                 anchors.fill: parent;
                 hoverEnabled: true;

From 66bf45c3ef1819b98ec880c4559592f94660a571 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Wed, 19 Sep 2018 15:18:18 -0700
Subject: [PATCH 26/56] Update location only if certificate id

---
 interface/src/commerce/QmlCommerce.cpp | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp
index 10be228310..7c5df0f3e3 100644
--- a/interface/src/commerce/QmlCommerce.cpp
+++ b/interface/src/commerce/QmlCommerce.cpp
@@ -199,12 +199,18 @@ void QmlCommerce::transferAssetToUsername(const QString& username,
 }
 
 void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) {
-    auto ledger = DependencyManager::get<Ledger>();
-    ledger->updateLocation(certificateID, DependencyManager::get<AddressManager>()->getPlaceName(), true);
+    if (!certificateID.isEmpty()) {
+        auto ledger = DependencyManager::get<Ledger>();
+        ledger->updateLocation(
+            certificateID,
+            DependencyManager::get<AddressManager>()->getPlaceName(),
+            true);
+    }
     qApp->replaceDomainContent(itemHref);
-    QJsonObject messageProperties = { { "status", "SuccessfulRequestToReplaceContent" }, { "content_set_url", itemHref } };
+    QJsonObject messageProperties = {
+        { "status", "SuccessfulRequestToReplaceContent" },
+        { "content_set_url", itemHref } };
     UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
-
     emit contentSetChanged(itemHref);
 }
 

From 38fd9523ee25081005753486dbd45d067080f358 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Wed, 19 Sep 2018 15:57:29 -0700
Subject: [PATCH 27/56] Attempt to load content set

This does not result in the user seeing the content set.
---
 .../MarketplaceItemTester.qml                     | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index 8b0ecf3436..69e1a6122c 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -55,6 +55,11 @@ Rectangle {
         }
     }
 
+    function isHttp(str) {
+        var httpPattern = /^http/i;
+        return httpPattern.test(str);
+    }
+
     Component.onCompleted: {
         // On startup, list includes all tester-installed assets.
         addAllInstalledAppsToList();
@@ -79,9 +84,15 @@ Rectangle {
                     switch(assetType) {
                         case "application":
                             Commerce.openApp(resource);
-                            break
+                            break;
                         case "avatar":
                             MyAvatar.useFullAvatarURL(resource);
+                            break;
+                        case "content set":
+                            resource = isHttp(resource) ? resource : "file:///" + resource;
+                            Commerce.replaceContentSet(resource, "");
+                            urlHandler.handleUrl("hifi://localhost/0,0,0");
+                            break;
                     }
                     // XXX support other resource types here.
                 },
@@ -119,7 +130,7 @@ Rectangle {
                 font.pointSize: 10
                 Layout.preferredWidth: root.width * .2
                 horizontalAlignment: Text.AlignHCenter
-                verticalAlignment: Test.AlignVCenter
+                verticalAlignment: Text.AlignVCenter
             }
 
             Repeater {

From 5445d6f6704ab702947a4da45ddf2ec528e96bc5 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Wed, 19 Sep 2018 16:06:43 -0700
Subject: [PATCH 28/56] Switch to localhost before replacing content

---
 .../commerce/marketplaceItemTester/MarketplaceItemTester.qml    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index 69e1a6122c..23c3bc976f 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -90,8 +90,8 @@ Rectangle {
                             break;
                         case "content set":
                             resource = isHttp(resource) ? resource : "file:///" + resource;
-                            Commerce.replaceContentSet(resource, "");
                             urlHandler.handleUrl("hifi://localhost/0,0,0");
+                            Commerce.replaceContentSet(resource, "");
                             break;
                     }
                     // XXX support other resource types here.

From afa8e44e66e359475ec07b68f1cc0f72121f0096 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Thu, 20 Sep 2018 10:53:32 -0700
Subject: [PATCH 29/56] Refactor

---
 .../marketplaceItemTester/MarketplaceItemTester.qml      | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index 23c3bc976f..d582e9aa82 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -55,9 +55,9 @@ Rectangle {
         }
     }
 
-    function isHttp(str) {
+    function toUrl(resource) {
         var httpPattern = /^http/i;
-        return httpPattern.test(str);
+        return httpPattern.test(str) ? resource : "file:///" + resource;
     }
 
     Component.onCompleted: {
@@ -89,9 +89,10 @@ Rectangle {
                             MyAvatar.useFullAvatarURL(resource);
                             break;
                         case "content set":
-                            resource = isHttp(resource) ? resource : "file:///" + resource;
                             urlHandler.handleUrl("hifi://localhost/0,0,0");
-                            Commerce.replaceContentSet(resource, "");
+                            Commerce.replaceContentSet(toUrl(resource), "");
+                            break;
+                        case "entity":
                             break;
                     }
                     // XXX support other resource types here.

From 72adfb7ff09ea4f708ff1edae0f3062e85882f4a Mon Sep 17 00:00:00 2001
From: Ryan Huffman <ryanhuffman@gmail.com>
Date: Tue, 18 Sep 2018 11:08:40 -0700
Subject: [PATCH 30/56] Fix entity list selection failing when selecting
 entities not in list

---
 scripts/system/html/js/entityList.js | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js
index 9cfdf6df22..0e025ef4c4 100644
--- a/scripts/system/html/js/entityList.js
+++ b/scripts/system/html/js/entityList.js
@@ -161,7 +161,10 @@ function loaded() {
 
             selectedEntities.forEach(function(entityID) {
                 if (selection.indexOf(entityID) === -1) {
-                    entitiesByID[entityID].el.className = '';
+                    let entity = entitiesByID[entityID];
+                    if (entity !== undefined) {
+                        entity.el.className = '';
+                    }
                 }
             });
 
@@ -385,15 +388,18 @@ function loaded() {
             let notFound = false;
 
             selectedEntities.forEach(function(id) {
-                entitiesByID[id].el.className = '';
+                let entity = entitiesByID[id];
+                if (entity !== undefined) {
+                    entity.el.className = '';
+                }
             });
 
             selectedEntities = [];
             for (let i = 0; i < selectedIDs.length; i++) {
                 let id = selectedIDs[i];
                 selectedEntities.push(id);
-                if (id in entitiesByID) {
-                    let entity = entitiesByID[id];
+                let entity = entitiesByID[id];
+                if (entity !== undefined) {
                     entity.el.className = 'selected';
                 } else {
                     notFound = true;

From ae4352f450ba93ecf448b1f74434ecc7614800c9 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Thu, 20 Sep 2018 13:55:22 -0700
Subject: [PATCH 31/56] Rez entity in marketplace item tester

---
 .../MarketplaceItemTester.qml                 | 16 +++++-
 scripts/system/marketplaces/marketplaces.js   | 51 ++++++++++++++-----
 2 files changed, 51 insertions(+), 16 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index d582e9aa82..9927c072db 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -23,7 +23,10 @@ import "../../../controls-uit" as HifiControlsUit
 
 Rectangle {
     id: root
+
     property string installedApps
+    signal sendToScript(var message)
+
     HifiStylesUit.HifiConstants { id: hifi }
     ListModel { id: resourceListModel }
 
@@ -57,7 +60,11 @@ Rectangle {
 
     function toUrl(resource) {
         var httpPattern = /^http/i;
-        return httpPattern.test(str) ? resource : "file:///" + resource;
+        return httpPattern.test(resource) ? resource : "file:///" + resource;
+    }
+
+    function rezEntity(itemHref, itemType) {
+
     }
 
     Component.onCompleted: {
@@ -92,7 +99,12 @@ Rectangle {
                             urlHandler.handleUrl("hifi://localhost/0,0,0");
                             Commerce.replaceContentSet(toUrl(resource), "");
                             break;
-                        case "entity":
+                        case "entity or wearable":
+                            print("going to rez " + toUrl(resource));
+                            sendToScript({
+                                method: 'tester_rezClicked',
+                                itemHref: toUrl(resource),
+                                itemType: "entity"});
                             break;
                     }
                     // XXX support other resource types here.
diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js
index 7b4f05193f..01c21044d2 100644
--- a/scripts/system/marketplaces/marketplaces.js
+++ b/scripts/system/marketplaces/marketplaces.js
@@ -21,20 +21,20 @@ var selectionDisplay = null; // for gridTool.js to ignore
     Script.include("/~/system/libraries/gridTool.js");
     Script.include("/~/system/libraries/connectionUtils.js");
 
-    var METAVERSE_SERVER_URL = Account.metaverseServerURL;
+    // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
+    var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
+    var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml";
+    var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml";
+    var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml";
+    var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
     var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace";
     var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?";  // Append "?" to signal injected script that it's the initial page.
-    var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html");
-    var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
-    var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml";
-    var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
     var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml";
-    var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml";
+    var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
+    var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html");
+    var METAVERSE_SERVER_URL = Account.metaverseServerURL;
     var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav"));
 
-    var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
-    // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
-
     // Event bridge messages.
     var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD";
     var CLARA_IO_STATUS = "CLARA.IO STATUS";
@@ -786,7 +786,7 @@ var selectionDisplay = null; // for gridTool.js to ignore
         }
         sendAssetRecipient = null;
     }
-    
+
     var savedDisablePreviewOptionLocked = false;
     var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview");;
     function maybeEnableHMDPreview() {
@@ -855,6 +855,8 @@ var selectionDisplay = null; // for gridTool.js to ignore
                 break;
             case 'checkout_rezClicked':
             case 'purchases_rezClicked':
+            case 'tester_rezClicked':
+                print("marketplace.js going to rez");
                 rezEntity(message.itemHref, message.itemType);
                 break;
             case 'header_marketplaceImageClicked':
@@ -1032,19 +1034,40 @@ var selectionDisplay = null; // for gridTool.js to ignore
     //    value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML.
     var onWalletScreen = false;
     var onCommerceScreen = false;
+    var onMarketplaceItemTesterScreen = false;
     function onTabletScreenChanged(type, url) {
         onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1;
         var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1;
-        var onCommerceScreenNow = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH
-            || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1);
+        var onCommerceScreenNow = type === "QML" && (
+            url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 ||
+            url === MARKETPLACE_PURCHASES_QML_PATH ||
+            url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1);
+        var onMarketplaceItemTesterScreenNow = (
+            url.indexOf(MARKETPLACE_ITEM_TESTER_QML_PATH) !== -1 ||
+            url === MARKETPLACE_ITEM_TESTER_QML_PATH);
 
-        if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { // exiting wallet or commerce screen
+        if ((!onWalletScreenNow && onWalletScreen) ||
+            (!onCommerceScreenNow && onCommerceScreen) ||
+            (!onMarketplaceItemTesterScreenNow && onMarketplaceScreen)
+        ) {
+            // exiting wallet, commerce, or marketplace item tester screen
             maybeEnableHMDPreview();
         }
 
         onCommerceScreen = onCommerceScreenNow;
         onWalletScreen = onWalletScreenNow;
-        wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen);
+        onMarketplaceItemTesterScreen = onMarketplaceItemTesterScreenNow;
+
+        print("wire event bridge " + (onMarketplaceScreen ||
+            onCommerceScreen ||
+            onWalletScreen ||
+            onMarketplaceItemTesterScreen));
+
+        wireEventBridge(
+            onMarketplaceScreen ||
+            onCommerceScreen ||
+            onWalletScreen ||
+            onMarketplaceItemTesterScreen);
 
         if (url === MARKETPLACE_PURCHASES_QML_PATH) {
             sendToQml({

From c1cc031bd0c7ddcadb5215bb50ec264f81d8411c Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Thu, 20 Sep 2018 14:50:27 -0700
Subject: [PATCH 32/56] Remove cruft and refactor

---
 .../MarketplaceItemTester.qml                 | 24 +++++++++----------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index 9927c072db..37d2f2c170 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -45,8 +45,9 @@ Rectangle {
     function installResourceObj(resourceObj) {
         if ("application" == resourceObj["assetType"]) {
             Commerce.installApp(resourceObj["resource"]);
+        } else {
+            print("Cannot install resource object type " + resourceObj["assetType"]);
         }
-        // XXX support other asset types here
     }
 
     function addAllInstalledAppsToList() {
@@ -63,14 +64,16 @@ Rectangle {
         return httpPattern.test(resource) ? resource : "file:///" + resource;
     }
 
-    function rezEntity(itemHref, itemType) {
-
+    function rezEntity(resource, entityType) {
+        sendToScript({
+            method: 'tester_rezClicked',
+            itemHref: toUrl(resource),
+            itemType: entityType});
     }
 
     Component.onCompleted: {
         // On startup, list includes all tester-installed assets.
         addAllInstalledAppsToList();
-        // XXX support other asset types here
     }
 
     ListView {
@@ -99,21 +102,18 @@ Rectangle {
                             urlHandler.handleUrl("hifi://localhost/0,0,0");
                             Commerce.replaceContentSet(toUrl(resource), "");
                             break;
-                        case "entity or wearable":
-                            print("going to rez " + toUrl(resource));
-                            sendToScript({
-                                method: 'tester_rezClicked',
-                                itemHref: toUrl(resource),
-                                itemType: "entity"});
+                        case "entity":
+                        case "wearable":
+                            rezEntity(resource, assetType);
                             break;
+                        default:
+                            print("Marketplace item tester unsupported assetType " + assetType);
                     }
-                    // XXX support other resource types here.
                 },
                 "trash": function(){
                     if ("application" == assetType) {
                         Commerce.uninstallApp(resource);
                     }
-                    // XXX support other resource types here.
                     resourceListModel.remove(index);
                 }
             }

From f9c25f2f32220e4f22969b9d65e8750f82774499 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Thu, 20 Sep 2018 16:36:06 -0700
Subject: [PATCH 33/56] Allow user to select asset type in marketplace item
 tester

---
 .../MarketplaceItemTester.qml                 | 27 ++++++++++++++-----
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index 37d2f2c170..559b6cb29c 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -12,6 +12,7 @@
 //
 
 import QtQuick 2.5
+import QtQuick.Controls 1.4
 import QtQuick.Controls.Styles 1.4
 import QtQuick.Dialogs 1.0
 import QtQuick.Layouts 1.1
@@ -83,6 +84,7 @@ Rectangle {
         anchors.rightMargin: 12
         model: resourceListModel
         spacing: 5
+        interactive: false
 
         delegate: RowLayout {
             anchors.left: parent.left
@@ -138,12 +140,25 @@ Rectangle {
                 }
             }
 
-            Text {
-                text: assetType
-                font.pointSize: 10
+            ComboBox {
+                id: comboBox
+
                 Layout.preferredWidth: root.width * .2
-                horizontalAlignment: Text.AlignHCenter
-                verticalAlignment: Text.AlignVCenter
+
+                model: [
+                    "application",
+                    "avatar",
+                    "content set",
+                    "entity",
+                    "wearable",
+                    "unknown"
+                ]
+
+                currentIndex: ("entity or wearable" == assetType) ? model.indexOf("unknown") : model.indexOf(assetType)
+
+                Component.onCompleted: {
+                    onActivated.connect(function() { assetType = currentText; });
+                }
             }
 
             Repeater {
@@ -159,7 +174,7 @@ Rectangle {
                     MouseArea {
                         anchors.fill: parent
                         onClicked: {
-                            actions[modelData.name](resource, assetType);
+                            actions[modelData.name](resource, comboBox.currentText);
                         }
                     }
                 }

From 86e502637e581d6cdd9e5e6cc48cb37215891dd7 Mon Sep 17 00:00:00 2001
From: Seth Alves <seth.alves@gmail.com>
Date: Sun, 16 Sep 2018 12:18:48 -0700
Subject: [PATCH 34/56] Make Settings thread safe

---
 interface/src/avatar/MyAvatar.cpp       | 211 +++++++++++-------------
 interface/src/avatar/MyAvatar.h         |  18 ++
 libraries/shared/src/SettingManager.cpp | 105 ++++++++++--
 libraries/shared/src/SettingManager.h   |  21 ++-
 4 files changed, 224 insertions(+), 131 deletions(-)

diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index c47cfdb383..df7ec93b6a 100755
--- a/interface/src/avatar/MyAvatar.cpp
+++ b/interface/src/avatar/MyAvatar.cpp
@@ -91,6 +91,8 @@ const float MIN_SCALE_CHANGED_DELTA = 0.001f;
 const int MODE_READINGS_RING_BUFFER_SIZE = 500;
 const float CENTIMETERS_PER_METER = 100.0f;
 
+const QString AVATAR_SETTINGS_GROUP_NAME { "Avatar" };
+
 MyAvatar::MyAvatar(QThread* thread) :
     Avatar(thread),
     _yawSpeed(YAW_SPEED_DEFAULT),
@@ -118,7 +120,22 @@ MyAvatar::MyAvatar(QThread* thread) :
     _goToOrientation(),
     _prevShouldDrawHead(true),
     _audioListenerMode(FROM_HEAD),
-    _hmdAtRestDetector(glm::vec3(0), glm::quat())
+    _hmdAtRestDetector(glm::vec3(0), glm::quat()),
+    _dominantHandSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "dominantHand", DOMINANT_RIGHT_HAND),
+    _headPitchSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "", 0.0f),
+    _scaleSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "scale", _targetScale),
+    _yawSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "yawSpeed", _yawSpeed),
+    _pitchSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "pitchSpeed", _pitchSpeed),
+    _fullAvatarURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "fullAvatarURL",
+                          AvatarData::defaultFullAvatarModelUrl()),
+    _fullAvatarModelNameSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "fullAvatarModelName", _fullAvatarModelName),
+    _animGraphURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "animGraphURL", QUrl("")),
+    _displayNameSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "displayName", ""),
+    _collisionSoundURLSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "collisionSoundURL", QUrl(_collisionSoundURL)),
+    _useSnapTurnSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "useSnapTurn", _useSnapTurn),
+    _userHeightSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userHeight", DEFAULT_AVATAR_HEIGHT),
+    _flyingHMDSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "flyingHMD", _flyingPrefHMD),
+    _avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", _flyingPrefHMD)
 {
     _clientTraitsHandler = std::unique_ptr<ClientTraitsHandler>(new ClientTraitsHandler(this));
 
@@ -1135,88 +1152,80 @@ void MyAvatar::restoreRoleAnimation(const QString& role) {
 }
 
 void MyAvatar::saveAvatarUrl() {
-    Settings settings;
-    settings.beginGroup("Avatar");
-    if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid() ) {
-        settings.setValue("fullAvatarURL",
-                          _fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ?
-                          "" :
-                          _fullAvatarURLFromPreferences.toString());
+    if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid()) {
+        _fullAvatarURLSetting.set(_fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ?
+                                  "" :
+                                  _fullAvatarURLFromPreferences.toString());
+    }
+}
+
+void MyAvatar::resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex) {
+    // The original Settings interface saved avatar-entity array data like this:
+    // Avatar/avatarEntityData/size: 5
+    // Avatar/avatarEntityData/1/id: ...
+    // Avatar/avatarEntityData/1/properties: ...
+    // ...
+    // Avatar/avatarEntityData/5/id: ...
+    // Avatar/avatarEntityData/5/properties: ...
+    //
+    // Create Setting::Handles to mimic this.
+
+    while (_avatarEntityIDSettings.size() <= avatarEntityIndex) {
+        Setting::Handle<QUuid> idHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData"
+                                        << QString::number(avatarEntityIndex + 1) << "id", QUuid());
+        _avatarEntityIDSettings.push_back(idHandle);
+        Setting::Handle<QByteArray> dataHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData"
+                                               << QString::number(avatarEntityIndex + 1) << "properties", QByteArray());
+        _avatarEntityDataSettings.push_back(dataHandle);
     }
-    settings.endGroup();
 }
 
 void MyAvatar::saveData() {
-    Settings settings;
-    settings.beginGroup("Avatar");
-
-    settings.setValue("dominantHand", _dominantHand);
-    settings.setValue("headPitch", getHead()->getBasePitch());
-
-    settings.setValue("scale", _targetScale);
-
-    settings.setValue("yawSpeed", _yawSpeed);
-    settings.setValue("pitchSpeed", _pitchSpeed);
+    _dominantHandSetting.set(_dominantHand);
+    _headPitchSetting.set(getHead()->getBasePitch());
+    _scaleSetting.set(_targetScale);
+    _yawSpeedSetting.set(_yawSpeed);
+    _pitchSpeedSetting.set(_pitchSpeed);
 
     // only save the fullAvatarURL if it has not been overwritten on command line
     // (so the overrideURL is not valid), or it was overridden _and_ we specified
     // --replaceAvatarURL (so _saveAvatarOverrideUrl is true)
     if (qApp->getSaveAvatarOverrideUrl() || !qApp->getAvatarOverrideUrl().isValid() ) {
-        settings.setValue("fullAvatarURL",
-                      _fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ?
-                      "" :
-                      _fullAvatarURLFromPreferences.toString());
+        _fullAvatarURLSetting.set(_fullAvatarURLFromPreferences == AvatarData::defaultFullAvatarModelUrl() ?
+                                  "" :
+                                  _fullAvatarURLFromPreferences.toString());
     }
 
-    settings.setValue("fullAvatarModelName", _fullAvatarModelName);
-
+    _fullAvatarModelNameSetting.set(_fullAvatarModelName);
     QUrl animGraphUrl = _prefOverrideAnimGraphUrl.get();
-    settings.setValue("animGraphURL", animGraphUrl);
+    _animGraphURLSetting.set(animGraphUrl);
+    _displayNameSetting.set(_displayName);
+    _collisionSoundURLSetting.set(_collisionSoundURL);
+    _useSnapTurnSetting.set(_useSnapTurn);
+    _userHeightSetting.set(getUserHeight());
+    _flyingHMDSetting.set(getFlyingHMDPref());
 
-    settings.beginWriteArray("attachmentData");
-    for (int i = 0; i < _attachmentData.size(); i++) {
-        settings.setArrayIndex(i);
-        const AttachmentData& attachment = _attachmentData.at(i);
-        settings.setValue("modelURL", attachment.modelURL);
-        settings.setValue("jointName", attachment.jointName);
-        settings.setValue("translation_x", attachment.translation.x);
-        settings.setValue("translation_y", attachment.translation.y);
-        settings.setValue("translation_z", attachment.translation.z);
-        glm::vec3 eulers = safeEulerAngles(attachment.rotation);
-        settings.setValue("rotation_x", eulers.x);
-        settings.setValue("rotation_y", eulers.y);
-        settings.setValue("rotation_z", eulers.z);
-        settings.setValue("scale", attachment.scale);
-        settings.setValue("isSoft", attachment.isSoft);
-    }
-    settings.endArray();
-
-    settings.remove("avatarEntityData");
-    settings.beginWriteArray("avatarEntityData");
-    int avatarEntityIndex = 0;
     auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
     _avatarEntitiesLock.withReadLock([&] {
-        for (auto entityID : _avatarEntityData.keys()) {
-            if (hmdInterface->getCurrentTabletFrameID() == entityID) {
-                // don't persist the tablet between domains / sessions
-                continue;
-            }
+        QList<QUuid> avatarEntityIDs = _avatarEntityData.keys();
+        unsigned int avatarEntityCount = avatarEntityIDs.size();
+        unsigned int previousAvatarEntityCount = _avatarEntityCountSetting.get(0);
+        resizeAvatarEntitySettingHandles(std::max<unsigned int>(avatarEntityCount, previousAvatarEntityCount));
+        _avatarEntityCountSetting.set(avatarEntityCount);
 
-            settings.setArrayIndex(avatarEntityIndex);
-            settings.setValue("id", entityID);
-            settings.setValue("properties", _avatarEntityData.value(entityID));
+        unsigned int avatarEntityIndex = 0;
+        for (auto entityID : avatarEntityIDs) {
+            _avatarEntityIDSettings[avatarEntityIndex].set(entityID);
+            _avatarEntityDataSettings[avatarEntityIndex].set(_avatarEntityData.value(entityID));
             avatarEntityIndex++;
         }
+
+        // clean up any left-over (due to the list shrinking) slots
+        for (; avatarEntityIndex < previousAvatarEntityCount; avatarEntityIndex++) {
+            _avatarEntityIDSettings[avatarEntityIndex].remove();
+            _avatarEntityDataSettings[avatarEntityIndex].remove();
+        }
     });
-    settings.endArray();
-
-    settings.setValue("displayName", _displayName);
-    settings.setValue("collisionSoundURL", _collisionSoundURL);
-    settings.setValue("useSnapTurn", _useSnapTurn);
-    settings.setValue("userHeight", getUserHeight());
-    settings.setValue("flyingHMD", getFlyingHMDPref());
-
-    settings.endGroup();
 }
 
 float loadSetting(Settings& settings, const QString& name, float defaultValue) {
@@ -1314,66 +1323,36 @@ void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
 }
 
 void MyAvatar::loadData() {
-    Settings settings;
-    settings.beginGroup("Avatar");
+    getHead()->setBasePitch(_headPitchSetting.get());
 
-    getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f));
+    _yawSpeed = _yawSpeedSetting.get(_yawSpeed);
+    _pitchSpeed = _pitchSpeedSetting.get(_pitchSpeed);
 
-    _yawSpeed = loadSetting(settings, "yawSpeed", _yawSpeed);
-    _pitchSpeed = loadSetting(settings, "pitchSpeed", _pitchSpeed);
-
-    _prefOverrideAnimGraphUrl.set(QUrl(settings.value("animGraphURL", "").toString()));
-    _fullAvatarURLFromPreferences = settings.value("fullAvatarURL", AvatarData::defaultFullAvatarModelUrl()).toUrl();
-    _fullAvatarModelName = settings.value("fullAvatarModelName", DEFAULT_FULL_AVATAR_MODEL_NAME).toString();
+    _prefOverrideAnimGraphUrl.set(_prefOverrideAnimGraphUrl.get().toString());
+    _fullAvatarURLFromPreferences = _fullAvatarURLSetting.get(QUrl(AvatarData::defaultFullAvatarModelUrl()));
+    _fullAvatarModelName = _fullAvatarModelNameSetting.get(DEFAULT_FULL_AVATAR_MODEL_NAME).toString();
 
     useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName);
 
-    int attachmentCount = settings.beginReadArray("attachmentData");
-    for (int i = 0; i < attachmentCount; i++) {
-        settings.setArrayIndex(i);
-        AttachmentData attachment;
-        attachment.modelURL = settings.value("modelURL").toUrl();
-        attachment.jointName = settings.value("jointName").toString();
-        attachment.translation.x = loadSetting(settings, "translation_x", 0.0f);
-        attachment.translation.y = loadSetting(settings, "translation_y", 0.0f);
-        attachment.translation.z = loadSetting(settings, "translation_z", 0.0f);
-        glm::vec3 eulers;
-        eulers.x = loadSetting(settings, "rotation_x", 0.0f);
-        eulers.y = loadSetting(settings, "rotation_y", 0.0f);
-        eulers.z = loadSetting(settings, "rotation_z", 0.0f);
-        attachment.rotation = glm::quat(eulers);
-        attachment.scale = loadSetting(settings, "scale", 1.0f);
-        attachment.isSoft = settings.value("isSoft").toBool();
-        // old attachments are stored and loaded/converted later when rig is ready
-        _oldAttachmentData.append(attachment);
-    }
-    settings.endArray();
-
-    int avatarEntityCount = settings.beginReadArray("avatarEntityData");
+    int avatarEntityCount = _avatarEntityCountSetting.get(0);
     for (int i = 0; i < avatarEntityCount; i++) {
-        settings.setArrayIndex(i);
-        QUuid entityID = settings.value("id").toUuid();
+        resizeAvatarEntitySettingHandles(i);
         // QUuid entityID = QUuid::createUuid(); // generate a new ID
-        QByteArray properties = settings.value("properties").toByteArray();
+        QUuid entityID = _avatarEntityIDSettings[i].get(QUuid());
+        QByteArray properties = _avatarEntityDataSettings[i].get();
         updateAvatarEntity(entityID, properties);
     }
-    settings.endArray();
-    if (avatarEntityCount == 0) {
-        // HACK: manually remove empty 'avatarEntityData' else legacy data may persist in settings file
-        settings.remove("avatarEntityData");
-    }
 
     // Flying preferences must be loaded before calling setFlyingEnabled()
     Setting::Handle<bool> firstRunVal { Settings::firstRun, true };
-    setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool());
+    setFlyingHMDPref(firstRunVal.get() ? false : _flyingHMDSetting.get());
     setFlyingEnabled(getFlyingEnabled());
 
-    setDisplayName(settings.value("displayName").toString());
-    setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString());
-    setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool());
-    setDominantHand(settings.value("dominantHand", _dominantHand).toString().toLower());
-    setUserHeight(settings.value("userHeight", DEFAULT_AVATAR_HEIGHT).toDouble());
-    settings.endGroup();
+    setDisplayName(_displayNameSetting.get());
+    setCollisionSoundURL(_collisionSoundURLSetting.get(QUrl(DEFAULT_AVATAR_COLLISION_SOUND_URL)).toString());
+    setSnapTurn(_useSnapTurnSetting.get());
+    setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower());
+    setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT));
 
     setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible));
     _follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing));
@@ -1442,6 +1421,7 @@ AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString&
     return attachment;
 }
 
+
 int MyAvatar::parseDataFromBuffer(const QByteArray& buffer) {
     qCDebug(interfaceapp) << "Error: ignoring update packet for MyAvatar"
         << " packetLength = " << buffer.size();
@@ -2899,10 +2879,7 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings
     }
 
     // Set avatar current scale
-    Settings settings;
-    settings.beginGroup("Avatar");
-    _targetScale = loadSetting(settings, "scale", 1.0f);
-
+    _targetScale = _scaleSetting.get();
     // clamp the desired _targetScale by the domain limits NOW, don't try to gracefully animate.  Because
     // this might cause our avatar to become embedded in the terrain.
     _targetScale = getDomainLimitedScale();
@@ -2914,7 +2891,6 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings
 
     setModelScale(_targetScale);
     rebuildCollisionShape();
-    settings.endGroup();
 
     _haveReceivedHeightLimitsFromDomain = true;
 }
@@ -2925,10 +2901,7 @@ void MyAvatar::leaveDomain() {
 }
 
 void MyAvatar::saveAvatarScale() {
-    Settings settings;
-    settings.beginGroup("Avatar");
-    settings.setValue("scale", _targetScale);
-    settings.endGroup();
+    _scaleSetting.set(_targetScale);
 }
 
 void MyAvatar::clearScaleRestriction() {
diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h
index 139f1f6ea2..1dc0b3cd40 100644
--- a/interface/src/avatar/MyAvatar.h
+++ b/interface/src/avatar/MyAvatar.h
@@ -550,6 +550,7 @@ public:
     float getHMDRollControlRate() const { return _hmdRollControlRate; }
 
     // get/set avatar data
+    void resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex);
     void saveData();
     void loadData();
 
@@ -1806,6 +1807,23 @@ private:
 
     bool _haveReceivedHeightLimitsFromDomain { false };
     int _disableHandTouchCount { 0 };
+
+    Setting::Handle<QString> _dominantHandSetting;
+    Setting::Handle<float> _headPitchSetting;
+    Setting::Handle<float> _scaleSetting;
+    Setting::Handle<float> _yawSpeedSetting;
+    Setting::Handle<float> _pitchSpeedSetting;
+    Setting::Handle<QUrl> _fullAvatarURLSetting;
+    Setting::Handle<QUrl> _fullAvatarModelNameSetting;
+    Setting::Handle<QUrl> _animGraphURLSetting;
+    Setting::Handle<QString> _displayNameSetting;
+    Setting::Handle<QUrl> _collisionSoundURLSetting;
+    Setting::Handle<bool> _useSnapTurnSetting;
+    Setting::Handle<float> _userHeightSetting;
+    Setting::Handle<bool> _flyingHMDSetting;
+    Setting::Handle<int> _avatarEntityCountSetting;
+    std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings;
+    std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings;
 };
 
 QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
diff --git a/libraries/shared/src/SettingManager.cpp b/libraries/shared/src/SettingManager.cpp
index 2e0850255a..e5920b785e 100644
--- a/libraries/shared/src/SettingManager.cpp
+++ b/libraries/shared/src/SettingManager.cpp
@@ -52,7 +52,7 @@ namespace Setting {
             if (_pendingChanges.contains(key) && _pendingChanges[key] != UNSET_VALUE) {
                 loadedValue = _pendingChanges[key];
             } else {
-                loadedValue = value(key);
+                loadedValue = _qSettings.value(key);
             }
             if (loadedValue.isValid()) {
                 handle->setVariant(loadedValue);
@@ -92,32 +92,115 @@ namespace Setting {
     }
 
     void Manager::saveAll() {
-        bool forceSync = false;
         withWriteLock([&] {
+            bool forceSync = false;
             for (auto key : _pendingChanges.keys()) {
                 auto newValue = _pendingChanges[key];
-                auto savedValue = value(key, UNSET_VALUE);
+                auto savedValue = _qSettings.value(key, UNSET_VALUE);
                 if (newValue == savedValue) {
                     continue;
                 }
+                forceSync = true;
                 if (newValue == UNSET_VALUE || !newValue.isValid()) {
-                    forceSync = true;
-                    remove(key);
+                    _qSettings.remove(key);
                 } else {
-                    forceSync = true;
-                    setValue(key, newValue);
+                    _qSettings.setValue(key, newValue);
                 }
             }
             _pendingChanges.clear();
-        });
 
-        if (forceSync) {
-            sync();
-        }
+            if (forceSync) {
+                _qSettings.sync();
+            }
+        });
 
         // Restart timer
         if (_saveTimer) {
             _saveTimer->start();
         }
     }
+
+    QString Manager::fileName() const {
+        return resultWithReadLock<QString>([&] {
+            return _qSettings.fileName();
+        });
+    }
+
+    void Manager::remove(const QString &key) {
+        withWriteLock([&] {
+            _qSettings.remove(key);
+        });
+    }
+
+    QStringList Manager::childGroups() const {
+        return resultWithReadLock<QStringList>([&] {
+            return _qSettings.childGroups();
+        });
+    }
+
+    QStringList Manager::childKeys() const {
+        return resultWithReadLock<QStringList>([&] {
+            return _qSettings.childKeys();
+        });
+    }
+
+    QStringList Manager::allKeys() const {
+        return resultWithReadLock<QStringList>([&] {
+            return _qSettings.allKeys();
+        });
+    }
+
+    bool Manager::contains(const QString &key) const {
+        return resultWithReadLock<bool>([&] {
+            return _qSettings.contains(key);
+        });
+    }
+
+    int Manager::beginReadArray(const QString &prefix) {
+        return resultWithReadLock<int>([&] {
+            return _qSettings.beginReadArray(prefix);
+        });
+    }
+
+    void Manager::beginGroup(const QString &prefix) {
+        withWriteLock([&] {
+            _qSettings.beginGroup(prefix);
+        });
+    }
+
+    void Manager::beginWriteArray(const QString &prefix, int size) {
+        withWriteLock([&] {
+            _qSettings.beginWriteArray(prefix, size);
+        });
+    }
+
+    void Manager::endArray() {
+        withWriteLock([&] {
+            _qSettings.endArray();
+        });
+    }
+
+    void Manager::endGroup() {
+        withWriteLock([&] {
+            _qSettings.endGroup();
+        });
+    }
+
+    void Manager::setArrayIndex(int i) {
+        withWriteLock([&] {
+            _qSettings.setArrayIndex(i);
+        });
+    }
+
+    void Manager::setValue(const QString &key, const QVariant &value) {
+        withWriteLock([&] {
+            _qSettings.setValue(key, value);
+        });
+    }
+
+    QVariant Manager::value(const QString &key, const QVariant &defaultValue) const {
+        return resultWithReadLock<QVariant>([&] {
+            return _qSettings.value(key, defaultValue);
+        });
+    }
 }
diff --git a/libraries/shared/src/SettingManager.h b/libraries/shared/src/SettingManager.h
index 6696a1ecf4..49f3bece4b 100644
--- a/libraries/shared/src/SettingManager.h
+++ b/libraries/shared/src/SettingManager.h
@@ -23,12 +23,28 @@
 namespace Setting {
     class Interface;
 
-    class Manager : public QSettings, public ReadWriteLockable, public Dependency {
+    class Manager : public QObject, public ReadWriteLockable, public Dependency {
         Q_OBJECT
 
     public:
         void customDeleter() override;
 
+        // thread-safe proxies into QSettings
+        QString fileName() const;
+        void remove(const QString &key);
+        QStringList childGroups() const;
+        QStringList childKeys() const;
+        QStringList allKeys() const;
+        bool contains(const QString &key) const;
+        int beginReadArray(const QString &prefix);
+        void beginGroup(const QString &prefix);
+        void beginWriteArray(const QString &prefix, int size = -1);
+        void endArray();
+        void endGroup();
+        void setArrayIndex(int i);
+        void setValue(const QString &key, const QVariant &value);
+        QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
+
     protected:
         ~Manager();
         void registerHandle(Interface* handle);
@@ -52,6 +68,9 @@ namespace Setting {
         friend class Interface;
         friend void cleanupSettingsSaveThread();
         friend void setupSettingsSaveThread();
+
+
+        QSettings _qSettings;
     };
 }
 

From d4450ac7801b043cf885c77a61a1c6db5ce21a2a Mon Sep 17 00:00:00 2001
From: Dante Ruiz <danteruiz102@gmail.com>
Date: Fri, 21 Sep 2018 15:59:00 -0700
Subject: [PATCH 35/56] fix equip and keeping new functionallity

---
 .../controllers/controllerModules/highlightNearbyEntities.js    | 2 +-
 scripts/system/controllers/controllerModules/inEditMode.js      | 2 +-
 scripts/system/controllers/controllerModules/inVREditMode.js    | 2 +-
 .../controllers/controllerModules/nearActionGrabEntity.js       | 2 +-
 .../controllers/controllerModules/nearGrabHyperLinkEntity.js    | 2 +-
 .../controllers/controllerModules/nearParentGrabEntity.js       | 2 +-
 scripts/system/controllers/controllerModules/nearTrigger.js     | 2 +-
 .../controllers/controllerModules/webSurfaceLaserInput.js       | 2 +-
 8 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js
index 3a33082f64..bc09ebee7a 100644
--- a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js
+++ b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js
@@ -37,7 +37,7 @@
         this.highlightedEntities = [];
 
         this.parameters = dispatcherUtils.makeDispatcherModuleParameters(
-            120,
+            480,
             this.hand === dispatcherUtils.RIGHT_HAND ? ["rightHand"] : ["leftHand"],
             [],
             100);
diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js
index 2bdd89f141..d590545532 100644
--- a/scripts/system/controllers/controllerModules/inEditMode.js
+++ b/scripts/system/controllers/controllerModules/inEditMode.js
@@ -29,7 +29,7 @@ Script.include("/~/system/libraries/utils.js");
         this.reticleMaxY;
 
         this.parameters = makeDispatcherModuleParameters(
-            200,
+            160,
             this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"],
             [],
             100,
diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js
index 02863cf935..7b78d5e1c4 100644
--- a/scripts/system/controllers/controllerModules/inVREditMode.js
+++ b/scripts/system/controllers/controllerModules/inVREditMode.js
@@ -21,7 +21,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
         this.disableModules = false;
         var NO_HAND_LASER = -1; // Invalid hand parameter so that default laser is not displayed.
         this.parameters = makeDispatcherModuleParameters(
-            240, // Not too high otherwise the tablet laser doesn't work.
+            200, // Not too high otherwise the tablet laser doesn't work.
             this.hand === RIGHT_HAND
                 ? ["rightHand", "rightHandEquip", "rightHandTrigger"]
                 : ["leftHand", "leftHandEquip", "leftHandTrigger"],
diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js
index 27c1b458b8..a8de76aebd 100644
--- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js
+++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js
@@ -26,7 +26,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
         this.hapticTargetID = null;
 
         this.parameters = makeDispatcherModuleParameters(
-            140,
+            500,
             this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
             [],
             100);
diff --git a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js
index 366fcd3032..962ae89bb9 100644
--- a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js
+++ b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js
@@ -21,7 +21,7 @@
         this.hyperlink = "";
 
         this.parameters = makeDispatcherModuleParameters(
-            125,
+            485,
             this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
             [],
             100);
diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js
index f805dbf60e..cc88371441 100644
--- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js
+++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js
@@ -57,7 +57,7 @@ Script.include("/~/system/libraries/controllers.js");
         this.cloneAllowed = true;
 
         this.parameters = makeDispatcherModuleParameters(
-            140,
+            500,
             this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
             [],
             100);
diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js
index f1126dedc3..6a9cd9fbcd 100644
--- a/scripts/system/controllers/controllerModules/nearTrigger.js
+++ b/scripts/system/controllers/controllerModules/nearTrigger.js
@@ -29,7 +29,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
         this.startSent = false;
 
         this.parameters = makeDispatcherModuleParameters(
-            120,
+            480,
             this.hand === RIGHT_HAND ? ["rightHandTrigger", "rightHand"] : ["leftHandTrigger", "leftHand"],
             [],
             100);
diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js
index 4e36355621..2412e2fa1c 100644
--- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js
+++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js
@@ -121,7 +121,7 @@ Script.include("/~/system/libraries/controllers.js");
                                    controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE;
             var allowThisModule = !otherModuleRunning || isTriggerPressed;
 
-            if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) {
+            if ((allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) && !this.grabModuleWantsNearbyOverlay(controllerData)) {
                 this.updateAllwaysOn();
                 if (isTriggerPressed) {
                     this.dominantHandOverride = true; // Override dominant hand.

From d772cdb34a1491196381752b682dcffbabdb7a1f Mon Sep 17 00:00:00 2001
From: SamGondelman <samuel_gondelman@alumni.brown.edu>
Date: Mon, 24 Sep 2018 10:16:35 -0700
Subject: [PATCH 36/56] please

---
 libraries/render-utils/src/Model.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
index b9ed43c339..ab6507b29c 100644
--- a/libraries/render-utils/src/Model.cpp
+++ b/libraries/render-utils/src/Model.cpp
@@ -1553,14 +1553,13 @@ void Model::setBlendedVertices(int blendNumber, const QVector<glm::vec3>& vertic
     for (int i = 0; i < fbxGeometry.meshes.size(); i++) {
         const FBXMesh& mesh = fbxGeometry.meshes.at(i);
         auto meshNormalsAndTangents = _normalsAndTangents.find(i);
-        if (mesh.blendshapes.isEmpty() || meshNormalsAndTangents == _normalsAndTangents.end()) {
+        const auto& buffer = _blendedVertexBuffers.find(i);
+        if (mesh.blendshapes.isEmpty() || meshNormalsAndTangents == _normalsAndTangents.end() || buffer == _blendedVertexBuffers.end()) {
             continue;
         }
 
         const auto vertexCount = mesh.vertices.size();
         const auto verticesSize = vertexCount * sizeof(glm::vec3);
-        const auto& buffer = _blendedVertexBuffers.find(i);
-        assert(buffer != _blendedVertexBuffers.end());
         buffer->second->resize(mesh.vertices.size() * sizeof(glm::vec3) + meshNormalsAndTangents->second.size() * sizeof(NormalType));
         buffer->second->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3));
         buffer->second->setSubData(verticesSize, meshNormalsAndTangents->second.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType));

From b31837168d15d4e5090b41ff03eef443ca34c97f Mon Sep 17 00:00:00 2001
From: Stephen Birarda <commit@birarda.com>
Date: Mon, 24 Sep 2018 11:27:20 -0700
Subject: [PATCH 37/56] fix bad lock, optimize some operations, clarify comment

---
 interface/src/avatar/AvatarManager.cpp  | 4 +++-
 interface/src/avatar/AvatarManager.h    | 4 +++-
 libraries/avatars/src/AvatarHashMap.cpp | 2 +-
 libraries/avatars/src/AvatarHashMap.h   | 3 +--
 4 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp
index d92a5c81da..0fdd246d7b 100644
--- a/interface/src/avatar/AvatarManager.cpp
+++ b/interface/src/avatar/AvatarManager.cpp
@@ -464,6 +464,8 @@ void AvatarManager::clearOtherAvatars() {
     {
         QWriteLocker locker(&_hashLock);
 
+        removedAvatars.reserve(_avatarHash.size());
+
         auto avatarIterator =  _avatarHash.begin();
         while (avatarIterator != _avatarHash.end()) {
             auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value());
@@ -484,7 +486,7 @@ void AvatarManager::clearOtherAvatars() {
 void AvatarManager::deleteAllAvatars() {
     assert(_avatarsToChangeInPhysics.empty());
 
-    QReadLocker locker(&_hashLock);
+    QWriteLocker locker(&_hashLock);
     AvatarHash::iterator avatarIterator =  _avatarHash.begin();
     while (avatarIterator != _avatarHash.end()) {
         auto avatar = std::static_pointer_cast<OtherAvatar>(avatarIterator.value());
diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h
index 407b3c50de..9c4287728d 100644
--- a/interface/src/avatar/AvatarManager.h
+++ b/interface/src/avatar/AvatarManager.h
@@ -205,7 +205,9 @@ private:
 
     AvatarSharedPointer newSharedAvatar() override;
     
-    // must not be called while holding the hash lock
+    // called only from the AvatarHashMap thread - cannot be called while this thread holds the
+    // hash lock, since handleRemovedAvatar needs a write lock on the entity tree and the entity tree
+    // frequently grabs a read lock on the hash to get a given avatar by ID
     void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar,
                              KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
 
diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp
index 2ee51cca17..01557e307e 100644
--- a/libraries/avatars/src/AvatarHashMap.cpp
+++ b/libraries/avatars/src/AvatarHashMap.cpp
@@ -73,7 +73,7 @@ std::vector<AvatarSharedPointer> AvatarReplicas::takeReplicas(const QUuid& paren
 
     if (it != _replicasMap.end()) {
         // take a copy of the replica shared pointers for this parent
-        replicas = it->second;
+        replicas.swap(it->second);
 
         // erase the replicas for this parent from our map
         _replicasMap.erase(it);
diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h
index 724dd1deac..c2cb448e52 100644
--- a/libraries/avatars/src/AvatarHashMap.h
+++ b/libraries/avatars/src/AvatarHashMap.h
@@ -180,8 +180,7 @@ protected:
         bool& isNew);
     virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock
     virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason);
-
-    // must not be called while holding the hash lock
+    
     virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason);
     
     AvatarHash _avatarHash;

From ad73cb3996c08a3bb43b80bac7c2c487727be26a Mon Sep 17 00:00:00 2001
From: SamGondelman <samuel_gondelman@alumni.brown.edu>
Date: Mon, 24 Sep 2018 14:02:35 -0700
Subject: [PATCH 38/56] fix avatar highlighting

---
 .../render-utils/src/HighlightEffect.cpp      | 45 ++++++++++---------
 libraries/render-utils/src/HighlightEffect.h  |  2 -
 2 files changed, 25 insertions(+), 22 deletions(-)

diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp
index 11326b1120..bcac31dd5a 100644
--- a/libraries/render-utils/src/HighlightEffect.cpp
+++ b/libraries/render-utils/src/HighlightEffect.cpp
@@ -37,6 +37,8 @@ namespace gr {
 
 #define OUTLINE_STENCIL_MASK    1
 
+extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state);
+
 HighlightRessources::HighlightRessources() {
 }
 
@@ -180,6 +182,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c
 
             auto maskPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder);
             auto maskSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned());
+            auto maskSkinnedDQPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned().withDualQuatSkinned());
 
             // Setup camera, projection and viewport for all items
             batch.setViewportTransform(args->_viewport);
@@ -187,14 +190,17 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c
             batch.setProjectionJitter(jitter.x, jitter.y);
             batch.setViewTransform(viewMat);
 
-            std::vector<ShapeKey> skinnedShapeKeys{};
+            std::vector<ShapeKey> skinnedShapeKeys;
+            std::vector<ShapeKey> skinnedDQShapeKeys;
 
             // Iterate through all inShapes and render the unskinned
             args->_shapePipeline = maskPipeline;
             batch.setPipeline(maskPipeline->pipeline);
             for (const auto& items : inShapes) {
                 itemBounds.insert(itemBounds.end(), items.second.begin(), items.second.end());
-                if (items.first.isSkinned()) {
+                if (items.first.isSkinned() && items.first.isDualQuatSkinned()) {
+                    skinnedDQShapeKeys.push_back(items.first);
+                } else if (items.first.isSkinned()) {
                     skinnedShapeKeys.push_back(items.first);
                 } else {
                     renderItems(renderContext, items.second);
@@ -202,10 +208,21 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c
             }
 
             // Reiterate to render the skinned
-            args->_shapePipeline = maskSkinnedPipeline;
-            batch.setPipeline(maskSkinnedPipeline->pipeline);
-            for (const auto& key : skinnedShapeKeys) {
-                renderItems(renderContext, inShapes.at(key));
+            if (skinnedShapeKeys.size() > 0) {
+                args->_shapePipeline = maskSkinnedPipeline;
+                batch.setPipeline(maskSkinnedPipeline->pipeline);
+                for (const auto& key : skinnedShapeKeys) {
+                    renderItems(renderContext, inShapes.at(key));
+                }
+            }
+
+            // Reiterate to render the DQ skinned
+            if (skinnedDQShapeKeys.size() > 0) {
+                args->_shapePipeline = maskSkinnedDQPipeline;
+                batch.setPipeline(maskSkinnedDQPipeline->pipeline);
+                for (const auto& key : skinnedDQShapeKeys) {
+                    renderItems(renderContext, inShapes.at(key));
+                }
             }
 
             args->_shapePipeline = nullptr;
@@ -488,7 +505,7 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren
         state->setDepthTest(true, true, gpu::LESS_EQUAL);
         state->setColorWriteMask(false, false, false, false);
 
-        initMaskPipelines(*shapePlumber, state);
+        initZPassPipelines(*shapePlumber, state);
     }
     auto sharedParameters = std::make_shared<HighlightSharedParameters>();
 
@@ -548,16 +565,4 @@ const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const
     const auto selectedMetasAndOpaques = task.addJob<SelectItems>("OpaqueSelection", selectMetaAndOpaqueInput);
     const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, selectionName).asVarying();
     return task.addJob<SelectItems>("TransparentSelection", selectItemInput);
-}
-
-void DrawHighlightTask::initMaskPipelines(render::ShapePlumber& shapePlumber, gpu::StatePointer state) {
-    gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(shader::render_utils::program::model_shadow);
-    shapePlumber.addPipeline(
-        ShapeKey::Filter::Builder().withoutSkinned(),
-        modelProgram, state);
-
-    gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(shader::render_utils::program::skin_model_shadow);
-    shapePlumber.addPipeline(
-        ShapeKey::Filter::Builder().withSkinned(),
-        skinProgram, state);
-}
+}
\ No newline at end of file
diff --git a/libraries/render-utils/src/HighlightEffect.h b/libraries/render-utils/src/HighlightEffect.h
index 64a97a549e..32668c1ab6 100644
--- a/libraries/render-utils/src/HighlightEffect.h
+++ b/libraries/render-utils/src/HighlightEffect.h
@@ -208,8 +208,6 @@ public:
     void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs);
 
 private:
-
-    static void initMaskPipelines(render::ShapePlumber& plumber, gpu::StatePointer state);
     static const render::Varying addSelectItemJobs(JobModel& task, const render::Varying& selectionName, const RenderFetchCullSortTask::BucketList& items);
 
 };

From 72fc686ec1c1172d72947864c948d169ecbb23fc Mon Sep 17 00:00:00 2001
From: Brad Davis <bdavis@saintandreas.org>
Date: Mon, 24 Sep 2018 14:52:13 -0700
Subject: [PATCH 39/56] Fix timer managmement during shutdown

---
 interface/src/Application.cpp                 |  6 +++--
 libraries/audio-client/src/AudioClient.cpp    | 27 ++++++++++++++++---
 libraries/audio-client/src/AudioClient.h      |  2 ++
 .../audio-client/src/AudioPeakValues.cpp      |  6 +++++
 4 files changed, 36 insertions(+), 5 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 46cebc1661..4eac967428 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -1759,10 +1759,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
 
     // Make sure we don't time out during slow operations at startup
     updateHeartbeat();
-
     QTimer* settingsTimer = new QTimer();
     moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{
-        connect(qApp, &Application::beforeAboutToQuit, [this, settingsTimer]{
+        // This needs to run on the settings thread, so we need to pass the `settingsTimer` as the 
+        // receiver object, otherwise it will run on the application thread and trigger a warning
+        // about trying to kill the timer on the main thread.
+        connect(qApp, &Application::beforeAboutToQuit, settingsTimer, [this, settingsTimer]{
             // Disconnect the signal from the save settings
             QObject::disconnect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings);
             // Stop the settings timer
diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index 3a33eccc8a..6af74bc8e8 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -101,6 +101,13 @@ QList<QAudioDeviceInfo> getAvailableDevices(QAudio::Mode mode) {
 
 // now called from a background thread, to keep blocking operations off the audio thread
 void AudioClient::checkDevices() {
+    // Make sure we're not shutting down
+    Lock timerMutex(_checkDevicesMutex);
+    // If we HAVE shut down after we were queued, but prior to execution, early exit
+    if (nullptr == _checkDevicesTimer) {
+        return;
+    }
+
     auto inputDevices = getAvailableDevices(QAudio::AudioInput);
     auto outputDevices = getAvailableDevices(QAudio::AudioOutput);
 
@@ -278,8 +285,8 @@ void AudioClient::customDeleter() {
     _shouldRestartInputSetup = false;
 #endif
     stop();
-    _checkDevicesTimer->stop();
-    _checkPeakValuesTimer->stop();
+    //_checkDevicesTimer->stop();
+    //_checkPeakValuesTimer->stop();
 
     deleteLater();
 }
@@ -648,12 +655,26 @@ void AudioClient::start() {
 }
 
 void AudioClient::stop() {
-
     qCDebug(audioclient) << "AudioClient::stop(), requesting switchInputToAudioDevice() to shut down";
     switchInputToAudioDevice(QAudioDeviceInfo(), true);
 
     qCDebug(audioclient) << "AudioClient::stop(), requesting switchOutputToAudioDevice() to shut down";
     switchOutputToAudioDevice(QAudioDeviceInfo(), true);
+
+    // Stop triggering the checks
+    QObject::disconnect(_checkPeakValuesTimer, &QTimer::timeout, nullptr, nullptr);
+    QObject::disconnect(_checkDevicesTimer, &QTimer::timeout, nullptr, nullptr);
+
+    // Destruction of the pointers will occur when the parent object (this) is destroyed)
+    {
+        Lock lock(_checkDevicesMutex);
+        _checkDevicesTimer = nullptr;
+    }
+    {
+        Lock lock(_checkPeakValuesMutex);
+        _checkPeakValuesTimer = nullptr;
+    }
+
 #if defined(Q_OS_ANDROID)
     _checkInputTimer.stop();
     disconnect(&_checkInputTimer, &QTimer::timeout, 0, 0);
diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h
index 4640d7c045..f5fee6184c 100644
--- a/libraries/audio-client/src/AudioClient.h
+++ b/libraries/audio-client/src/AudioClient.h
@@ -432,7 +432,9 @@ private:
     bool _shouldRestartInputSetup { true }; // Should we restart the input device because of an unintended stop?
 #endif
     
+    Mutex _checkDevicesMutex;
     QTimer* _checkDevicesTimer { nullptr };
+    Mutex _checkPeakValuesMutex;
     QTimer* _checkPeakValuesTimer { nullptr };
 
     bool _isRecording { false };
diff --git a/libraries/audio-client/src/AudioPeakValues.cpp b/libraries/audio-client/src/AudioPeakValues.cpp
index 0b8921a117..a50567da7f 100644
--- a/libraries/audio-client/src/AudioPeakValues.cpp
+++ b/libraries/audio-client/src/AudioPeakValues.cpp
@@ -40,6 +40,12 @@ void release(IAudioClient* audioClient) {
 }
 
 void AudioClient::checkPeakValues() {
+    // Guard against running during shutdown
+    Lock timerMutex(_checkPeakValuesMutex);
+    if (nullptr == _checkPeakValuesTimer) {
+        return;
+    }
+
     // prepare the windows environment
     CoInitialize(NULL);
 

From 69d529936299331669c7913f5a4ca3f13b646be3 Mon Sep 17 00:00:00 2001
From: Wayne Chen <wayne@highfidelity.io>
Date: Mon, 24 Sep 2018 15:22:46 -0700
Subject: [PATCH 40/56] patching fix for previous commits

---
 interface/src/Application.cpp                 | 13 +++---
 interface/src/Application.h                   |  2 +-
 interface/src/ConnectionMonitor.cpp           | 22 +++-------
 interface/src/ConnectionMonitor.h             |  5 +--
 .../scripting/WindowScriptingInterface.cpp    |  8 ++++
 .../src/scripting/WindowScriptingInterface.h  |  4 ++
 libraries/networking/src/DomainHandler.cpp    | 43 ++++++++++++-------
 libraries/networking/src/DomainHandler.h      |  8 +++-
 8 files changed, 61 insertions(+), 44 deletions(-)

diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 46cebc1661..a9aa9077b0 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -3498,13 +3498,14 @@ bool Application::isServerlessMode() const {
 }
 
 void Application::setIsInterstitialMode(bool interstitialMode) {
-    Settings settings;
-    bool enableInterstitial = settings.value("enableIntersitialMode", false).toBool();
-    if (_interstitialMode != interstitialMode && enableInterstitial) {
-        _interstitialMode = interstitialMode;
+    bool enableInterstitial = DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled();
+    if (enableInterstitial) {
+        if (_interstitialMode != interstitialMode) {
+            _interstitialMode = interstitialMode;
 
-        DependencyManager::get<AudioClient>()->setAudioPaused(_interstitialMode);
-        DependencyManager::get<AvatarManager>()->setMyAvatarDataPacketsPaused(_interstitialMode);
+            DependencyManager::get<AudioClient>()->setAudioPaused(_interstitialMode);
+            DependencyManager::get<AvatarManager>()->setMyAvatarDataPacketsPaused(_interstitialMode);
+        }
     }
 }
 
diff --git a/interface/src/Application.h b/interface/src/Application.h
index 3bebc60480..eedbdb7622 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -432,7 +432,7 @@ public slots:
 
     void setIsServerlessMode(bool serverlessDomain);
     void loadServerlessDomain(QUrl domainURL, bool errorDomain = false);
-    void setIsInterstitialMode(bool interstialMode);
+    void setIsInterstitialMode(bool interstitialMode);
 
     void updateVerboseLogging();
 
diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp
index ca90d039f4..e86061b090 100644
--- a/interface/src/ConnectionMonitor.cpp
+++ b/interface/src/ConnectionMonitor.cpp
@@ -23,8 +23,6 @@
 // should be longer to allow the application to initialize.
 static const int ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 10000;
 static const int REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 5000;
-static const int ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 10000;
-static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000;
 
 void ConnectionMonitor::init() {
     // Connect to domain disconnected message
@@ -39,18 +37,15 @@ void ConnectionMonitor::init() {
 
     _timer.setSingleShot(true);
     if (!domainHandler.isConnected()) {
-        if (_enableInterstitialMode.get()) {
-            _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS);
-        } else {
-            _timer.start(ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS);
-        }
+        _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS);
     }
 
     connect(&_timer, &QTimer::timeout, this, [this]() {
         // set in a timeout error
-        if (_enableInterstitialMode.get()) {
+        bool enableInterstitial = DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled();
+        if (enableInterstitial) {
             qDebug() << "ConnectionMonitor: Redirecting to 404 error domain";
-            emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5);
+            emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, "", 5);
         } else {
             qDebug() << "ConnectionMonitor: Showing connection failure window";
             DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(true);
@@ -59,16 +54,13 @@ void ConnectionMonitor::init() {
 }
 
 void ConnectionMonitor::startTimer() {
-    if (_enableInterstitialMode.get()) {
-        _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS);
-    } else {
-        _timer.start(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS);
-    }
+    _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS);
 }
 
 void ConnectionMonitor::stopTimer() {
     _timer.stop();
-    if (!_enableInterstitialMode.get()) {
+    bool enableInterstitial = DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled();
+    if (!enableInterstitial) {
         DependencyManager::get<DialogsManager>()->setDomainConnectionFailureVisibility(false);
     }
 }
diff --git a/interface/src/ConnectionMonitor.h b/interface/src/ConnectionMonitor.h
index f0589a3b8c..2fda6ef7cd 100644
--- a/interface/src/ConnectionMonitor.h
+++ b/interface/src/ConnectionMonitor.h
@@ -15,8 +15,6 @@
 #include <QObject>
 #include <QTimer>
 
-#include <SettingHandle.h>
-
 class QUrl;
 class QString;
 
@@ -26,7 +24,7 @@ public:
     void init();
 
 signals:
-    void setRedirectErrorState(QUrl errorURL, int reasonCode);
+    void setRedirectErrorState(QUrl errorURL, QString reasonMessage = "", int reasonCode = -1, const QString& extraInfo = "");
 
 private slots:
     void startTimer();
@@ -34,7 +32,6 @@ private slots:
 
 private:
     QTimer _timer;
-    Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false };
 };
 
 #endif // hifi_ConnectionMonitor_h
diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp
index e3ae65aee1..d4eb37e0aa 100644
--- a/interface/src/scripting/WindowScriptingInterface.cpp
+++ b/interface/src/scripting/WindowScriptingInterface.cpp
@@ -180,6 +180,14 @@ void WindowScriptingInterface::setPreviousBrowseAssetLocation(const QString& loc
     Setting::Handle<QVariant>(LAST_BROWSE_ASSETS_LOCATION_SETTING).set(location);
 }
 
+bool WindowScriptingInterface::getInterstitialModeEnabled() const {
+    return DependencyManager::get<NodeList>()->getDomainHandler().getInterstitialModeEnabled();
+}
+
+void WindowScriptingInterface::setInterstitialModeEnabled(bool enableInterstitialMode) {
+    DependencyManager::get<NodeList>()->getDomainHandler().setInterstitialModeEnabled(enableInterstitialMode);
+}
+
 bool  WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) {
     auto offscreenUi = DependencyManager::get<OffscreenUi>();
     return offscreenUi->isPointOnDesktopWindow(point);
diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h
index 3827406729..ddd7159f23 100644
--- a/interface/src/scripting/WindowScriptingInterface.h
+++ b/interface/src/scripting/WindowScriptingInterface.h
@@ -49,6 +49,7 @@ class WindowScriptingInterface : public QObject, public Dependency {
     Q_PROPERTY(int innerHeight READ getInnerHeight)
     Q_PROPERTY(int x READ getX)
     Q_PROPERTY(int y READ getY)
+    Q_PROPERTY(bool interstitialModeEnabled READ getInterstitialModeEnabled WRITE setInterstitialModeEnabled)
 
 public:
     WindowScriptingInterface();
@@ -758,6 +759,9 @@ private:
     QString getPreviousBrowseAssetLocation() const;
     void setPreviousBrowseAssetLocation(const QString& location);
 
+    bool getInterstitialModeEnabled() const;
+    void setInterstitialModeEnabled(bool enableInterstitialMode);
+
     void ensureReticleVisible() const;
 
     int createMessageBox(QString title, QString text, int buttons, int defaultButton);
diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp
index b2f118c5c8..df34a1fb59 100644
--- a/libraries/networking/src/DomainHandler.cpp
+++ b/libraries/networking/src/DomainHandler.cpp
@@ -15,6 +15,10 @@
 
 #include <PathUtils.h>
 
+#include <shared/QtHelpers.h>
+
+#include <QThread>
+
 #include <QtCore/QJsonDocument>
 #include <QtCore/QDataStream>
 
@@ -134,6 +138,18 @@ void DomainHandler::hardReset() {
     _pendingPath.clear();
 }
 
+bool DomainHandler::getInterstitialModeEnabled() const {
+    return _interstitialModeSettingLock.resultWithReadLock<bool>([&] {
+        return _enableInterstitialMode.get();
+    });
+}
+
+void DomainHandler::setInterstitialModeEnabled(bool enableInterstitialMode) {
+    _interstitialModeSettingLock.withWriteLock([&] {
+        _enableInterstitialMode.set(enableInterstitialMode);
+    });
+}
+
 void DomainHandler::setErrorDomainURL(const QUrl& url) {
     _errorDomainURL = url;
     return;
@@ -340,11 +356,15 @@ void DomainHandler::loadedErrorDomain(std::map<QString, QString> namedPaths) {
     DependencyManager::get<AddressManager>()->goToViewpointForPath(viewpoint, QString());
 }
 
-void DomainHandler::setRedirectErrorState(QUrl errorUrl, int reasonCode) {
-    _errorDomainURL = errorUrl;
+void DomainHandler::setRedirectErrorState(QUrl errorUrl, QString reasonMessage, int reasonCode, const QString& extraInfo) {
     _lastDomainConnectionError = reasonCode;
-    _isInErrorState = true;
-    emit redirectToErrorDomainURL(_errorDomainURL);
+    if (getInterstitialModeEnabled()) {
+        _errorDomainURL = errorUrl;
+        _isInErrorState = true;
+        emit redirectToErrorDomainURL(_errorDomainURL);
+    } else {
+        emit domainConnectionRefused(reasonMessage, reasonCode, extraInfo);
+    }
 }
 
 void DomainHandler::requestDomainSettings() {
@@ -486,18 +506,9 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
         emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo);
 #else
 
-        if (_enableInterstitialMode.get()) {
-            if (reasonCode == ConnectionRefusedReason::ProtocolMismatch || reasonCode == ConnectionRefusedReason::NotAuthorized) {
-                // ingest the error - this is a "hard" connection refusal.
-                setRedirectErrorState(_errorDomainURL, (int)reasonCode);
-            } else {
-                emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo);
-            }
-            _lastDomainConnectionError = (int)reasonCode;
-        } else {
-            emit domainConnectionRefused(reasonMessage, (int)reasonCode, extraInfo);
-        }
-       #endif
+        // ingest the error - this is a "hard" connection refusal.
+        setRedirectErrorState(_errorDomainURL, reasonMessage, (int)reasonCode, extraInfo);
+#endif
     }
 
     auto accountManager = DependencyManager::get<AccountManager>();
diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h
index 5fa920a554..e9ec20ba2e 100644
--- a/libraries/networking/src/DomainHandler.h
+++ b/libraries/networking/src/DomainHandler.h
@@ -19,6 +19,7 @@
 #include <QtCore/QUrl>
 #include <QtNetwork/QHostInfo>
 
+#include <shared/ReadWriteLockable.h>
 #include <SettingHandle.h>
 
 #include "HifiSockAddr.h"
@@ -85,6 +86,8 @@ public:
     bool isConnected() const { return _isConnected; }
     void setIsConnected(bool isConnected);
     bool isServerless() const { return _domainURL.scheme() != URL_SCHEME_HIFI; }
+    bool getInterstitialModeEnabled() const;
+    void setInterstitialModeEnabled(bool enableInterstitialMode);
 
     void connectedToServerless(std::map<QString, QString> namedPaths);
 
@@ -173,7 +176,7 @@ public slots:
     void processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message);
 
     // sets domain handler in error state.
-    void setRedirectErrorState(QUrl errorUrl, int reasonCode);
+    void setRedirectErrorState(QUrl errorUrl, QString reasonMessage = "", int reason = -1, const QString& extraInfo = "");
 
     bool isInErrorState() { return _isInErrorState; }
 
@@ -223,10 +226,11 @@ private:
     NetworkPeer _icePeer;
     bool _isConnected { false };
     bool _isInErrorState { false };
-    Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false };
     QJsonObject _settingsObject;
     QString _pendingPath;
     QTimer _settingsTimer;
+    mutable ReadWriteLockable _interstitialModeSettingLock;
+    Setting::Handle<bool> _enableInterstitialMode{ "enableInterstitialMode", false };
 
     QSet<QString> _domainConnectionRefusals;
     bool _hasCheckedForAccessToken { false };

From 697f556a60211c48797f4bc5f685589e0a0ec229 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Mon, 24 Sep 2018 16:30:49 -0700
Subject: [PATCH 41/56] Persist list of marketplace items in test

The list of resources between runs of the marketplace item tester now persist between runs of the tester.
---
 .../MarketplaceItemTester.qml                 |  54 +++++++++++++-----
 .../marketplaceItemTester/spinner.gif         | Bin 0 -> 46135 bytes
 scripts/system/marketplaces/marketplaces.js   |  52 ++++++++++++++++-
 3 files changed, 91 insertions(+), 15 deletions(-)
 create mode 100644 interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index 559b6cb29c..f2dbcc3f80 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -29,10 +29,35 @@ Rectangle {
     signal sendToScript(var message)
 
     HifiStylesUit.HifiConstants { id: hifi }
-    ListModel { id: resourceListModel }
+    ListModel {
+        id: resourceListModel
+        property var nextId: 0
+    }
 
     color: hifi.colors.white
 
+    AnimatedImage {
+        id: spinner;
+        source: "spinner.gif"
+        width: 74;
+        height: width;
+        anchors.verticalCenter: parent.verticalCenter;
+        anchors.horizontalCenter: parent.horizontalCenter;
+    }
+
+    function fromScript(message) {
+        switch (message.method) {
+            case "newResourceObjectInTest":
+                var resourceObject = message.resourceObject;
+                resourceListModel.append(resourceObject);
+                spinner.visible = false;
+                break;
+            case "marketplaceTestBackendIsAlive":
+                spinner.visible = false;
+                break;
+        }
+    }
+
     function buildResourceObj(resource) {
         resource = resource.trim();
         var assetType = (resource.match(/\.app\.json$/) ? "application" :
@@ -40,14 +65,14 @@ Rectangle {
                          resource.match(/\.json\.gz$/) ? "content set" :
                          resource.match(/\.json$/) ? "entity or wearable" :
                          "unknown");
-        return { "resource": resource, "assetType": assetType };
+        return { "id": resourceListModel.nextId++,
+                 "resource": resource,
+                 "assetType": assetType };
     }
 
     function installResourceObj(resourceObj) {
         if ("application" == resourceObj["assetType"]) {
             Commerce.installApp(resourceObj["resource"]);
-        } else {
-            print("Cannot install resource object type " + resourceObj["assetType"]);
         }
     }
 
@@ -72,11 +97,6 @@ Rectangle {
             itemType: entityType});
     }
 
-    Component.onCompleted: {
-        // On startup, list includes all tester-installed assets.
-        addAllInstalledAppsToList();
-    }
-
     ListView {
         anchors.fill: parent
         anchors.leftMargin: 12
@@ -157,7 +177,13 @@ Rectangle {
                 currentIndex: ("entity or wearable" == assetType) ? model.indexOf("unknown") : model.indexOf(assetType)
 
                 Component.onCompleted: {
-                    onActivated.connect(function() { assetType = currentText; });
+                    onCurrentIndexChanged.connect(function() {
+                        assetType = model[currentIndex];
+                        sendToScript({
+                            method: 'tester_updateResourceObjectAssetType',
+                            objectId: resourceListModel.get(index)["id"],
+                            assetType: assetType });
+                    });
                 }
             }
 
@@ -220,17 +246,17 @@ Rectangle {
                 // ahead as though we are sure the present signal is one
                 // we expect.
                 if ("load file" == currentAction) {
-                    print("disconnecting load file");
                     Window.browseChanged.disconnect(onResourceSelected);
                 } else if ("load url" == currentAction) {
-                    print("disconnecting load url");
                     Window.promptTextChanged.disconnect(onResourceSelected);
                 }
                 if (resource) {
                     var resourceObj = buildResourceObj(resource);
                     installResourceObj(resourceObj);
-                    resourceListModel.append(resourceObj);
-                }
+                    sendToScript({
+                        method: 'tester_newResourceObject',
+                        resourceObject: resourceObj });
+                    }
             }
 
             Repeater {
diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif b/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif
new file mode 100644
index 0000000000000000000000000000000000000000..00f75ae62fa59b28a7813ca00c4185390e3e51da
GIT binary patch
literal 46135
zcmdSCc|6p6|Nk%Rn1*JUu`e_BJ%lJa%x1_kZ^oL+9-<IJ6tXXch7dAi7lmj&b+Rul
z2MsN!QxPqybIPHsI_2}d@#?(K9M$=BUDxOO{64?OU)@^%%l-YhJ)V!}^JQ&AH8u0u
z%(I#2A3RG!qtPl{QvDy5+OpKu)iwT*<{#41($dz})>)RWuCAV*-rBWm4Gav{ty^bg
zWMpDuVrpt?W@ct?Zf;>=v1!w$@4oxaa#>c(BK{%LvdCmIl}fd-v9YtWb8v8=)9Fr5
zP7DUa)z#I_&CT82-NVDf)6>(-%WLb_t=qP3^Y-@k^YaS|3JMJk4G#~Gii(PfiP^h%
zZ(Lkle0==Aefts<6Zh}mf8fA@<mBX(WgR?tFg-mzD=RBEH}~-2!}<C71%Ifpu<*!{
zBTOc<sHmv8xcKPNqa`IJWo2c@jvXs6FR!SmsI084s;XkKSk=p_si~=5R$X0PLqkJj
zV`EcO(}@!&nwy(Xo;=yw+S=aU-qF!<_Uzg7=g(ifc=6JuOF#VZLswT<Pft&8Z*PBp
z|G>b&(9qEE@bI;3*KXXnF)}hTIy!pu=FN$TiCedB-MMq;?%lgT{q)n%KmYvT!Gnhn
zA5Kk8O;1nH%*;G~{P@X}Cr_U~efI3x?Ck9G=g(ifc=2*szbxz5WxZO~>({U6mh~H#
z=I7@Z78c$t>mUF4$J=GSU)Jw`)rY_8BbOGxuK)U96yd*CJ5U(TBr7__P*0z46%P*&
z;T=B@9}f?Y&e9LZ(vJxbVv*;(upF(ms_P&^T+3^)mDQ7hR<<ptwXyqhq}GMp8*Hl{
zIE**jv~wY=kiBV_Y1#+V=lMlALEm9UN2FiNCMWXXJ?SHmeeRW~S037McxsO0a#oc=
z9U7`N;*fmsU931Klj%<nyYQ^3CpgkFH0=G5Hfal^zdto%<`8>xJ+pLcX3pSknE*B1
zvES);j<l?M!T(4WTJ0honSeRekkM4&lG8S;vw;#ZozYbyF+TUgu-DA~_~60|^sOAW
zRd~Vvpt`3y&<Xi=CkRUq%?Qt)l}g;lDr6h&TbtlApBbK>mMyX2@`&xI+cn&Ke295`
zH-<dwZcQuKV80Yvzlf{gzwcemt{j%+XLJ<Br<~9FrQ(EL-pAiVw9Nb+v2$K7on{?0
zNb9<)0<_6E$_6TUwLpVhjg4%fj8{c+P>rdP9pwLtLWO=aHsXiOU)AzKBugw63W%E#
z&OldKGvot@Af_@};0SNl_aAGnWtrFN+mW645k-nj8-Hua2=&G}kTOYtbo8*sLQbz=
zPZM>-8zBx-Abnogj(o`qTI-2P<2#DK9!Y}MnVLa<l&m+S`gTSETPVY?Mwk*7fi)-z
zdl5_nxugVL79uk_=LV_y7mlVibfp()SkYZrR7H~+qFkgc7A2=kcS)yFoQg={7=mw)
zE0Z48U!WTkW=xhX)>FxrE!{=oua>_)_tf3?X+)NK{lhtwUVVa-je<=>z!mY+sWrJi
zV}YA-Ro~4JiqAZh(N9ruqeOb18@v39>2@N&P5FX-qotsBDvP)O;;VipQK~WajV*!k
z@K_j<f$k0rJ%^tgbJufEMy&nG;x}6x^{&wjC1%3du+gv4FJ2GeGj!$#r3Cmt|8JVX
z!^4BcVwIJZxoEzme-r2c;V{TymjA)=PaxOU)&>;U*Vi{NFfcSUT(>MEBO_yDV-pjT
z_3PK0F3Zf!Y{RlPE(-wO!omUo-pXnT?PM~ULZMKXWo>OuqtR?^Y;0|9H*el-XJ=<`
zZ|~sX;OOWGEz8;2nZaPVxVX5xyL)<iZr!?d`}XYs@Bsk<J9g{{4i4VAb7xps*zVoC
zBO)U9?Aa3?9Sy@iK0ZDnAt5m_@xXxtNl8g5DJiL`scC6x>FMbi85vnwS=rgyxw*NA
z4jn2eSR#E<QPI((M@vgf%gV|C-K(mq*lc!9O%1?#Q&STR^0v0N)2C0LIdkURxpNmT
zT=@R`?|=B=hwkp~-rnB6zP`c1L4fe<*RKPLkByCSIGl-ziMx02-n)12{{8z89z2+w
zoSd4Pdi3ZK;5UFbOzvNPSz`99S4*@8!2a#GFF^X;9|EX;|NaxL0i(Il`~`jfcfN@4
z|3z?rL5M}3mT$oH#==M@ZKTpX8EYehD}2;M3{1zD*qjc`R!{VF{V=0}$}Y1!I6SI}
zk;E%n9pQ~8&rhBr(TZ#<-ZcgHIM~;P)cO_?lqEbnZ#*k&JzC!CQ(9;o+l0I;<IUqT
z%W0Bmu+3g<ZR|W#rB&*@x1*6GsiddnoQR%vG4+*dRDJU>L~isDL$O4|`kIRIX6@^k
zdX1mErRGUw+xij1kFBnUYN)Pl`(xFgi58!heUnnafOcgZD%|kyWvks7wO4v71cSAX
zOwTJk6L&e>&dG*@<2|YQ$m2~aA!V`h-61r?>AUGQ)D4yQ@vNG%yOPaEn5RA@&mUa8
z@Lj9pnDYnW`EB$H{vi7(tF6A?sx}tBcy*hXkd$JKrQucbrDik(muR8fW2bC~8Bl7n
z<fl3{G<R;J`&lw*PRY&V)ZOULLXOz)(P<>QrWc}Mi&bBci;_(uVKm8doM*H1N!92~
z@<krPR_9^?9MMHgn2lB5;NnDd3Z`QNoOG}VF^%s9#(9&l0&J{UTf+2kjHQh`Mh^-r
zv9-SB#bT~G;t@4;)JA00FtyR2P_$d>BLOFZ^i8!{<J8$OD(S=;p=M(z+l5#*K9DE1
z4R7GGI`-{b`r3nZt#R>!&5`76G0#ikR24I=nqh^w#i3y9Q#_40(cLOzS$iis#)eeV
zN4h9TABvSNturclqSLvvEyv-<hUAxLjoPl%y5yo){nF`BatEiEsK585lLe~qxo1Pe
zXxBw!)&-fMTGUip`h!EQ6oUI>*Bs@Y=Brv5Bxdqp<+D7cET!a!ZPxfKOT(6AxxE0>
zk(OU(^ncWEtEw*XdI{3YmRoa~&cJEI9$SA|2Fo&B)(TPs0bZM#nQh#-5jb#5OG_eg
z$#27SrY#FbGdG<9pkW{G=H}+%;o;@w1-QIp$BvMYkkHUjK;)>XsJ(ml0ub-tzdt!S
z`QX8Wsi~;|!I{g-`a{{-**Q5mfXKjvA31WQxVRV)`Pi{zu-|60+11t6u+y%uuje+~
zEiEmu**<;xbVo-==d#Y8J$vrlx%11qaN)wmix<ED{`<?9F9QeO)z#JA-3@$rUtizA
zz`)?(;Ly;})vH%wg5SJ(b9{XK)~#E&Z{G$c960btj~)RN{$yECmj!${u;MRYeu3um
z%UbwDz<9&<8#wSk*#1c?5dZt{E3Np44}TK>izxr6^aU7yMTkY7nQvh<ai404Q<Yh=
zVMT~UVYRN!j+#t;%hQ3^<(#f1Oie`k>YLkyBIP~zSWDU8j5q03%@7=|cMNz4mC;>g
zaP-q+MWlr)m1fVLiRQ@@+FgPEy-l^j123+)K@&|a5!YJ2cQfyuc*SeO(DEAbP0Q}E
zw!2>z_u{b(yQuf5y$vpsujuiBXmQ}-zO)ctly+9rJ%`hUott$oV$WM{Wg(0y6gn$4
zMmOT_;d)5_=AoxgGz+{gsw!NyVho&kDE7qsWvaQ-`YF;yqr(kZ4Xd}@B*YVJHGc02
z)J|V~{P5W&!V6aL@Wx&ulleZ~FkK?<$8br=U|up?>4N$X-M<mTrnVEK(9w63goX^5
zNQ6?<0NunWc0`d}pgw7tiB`Ru#&1L9Xvm573PWkB8gxCf%?`G?edbxVc`!O)IJ*Qb
zFpxc1qK`%OHPi0Wouer<T%kLyAcORpN5rM;w&f9YJijw$Jq<l0=wfh}pCmJ2A2k?h
zQR~DDW!W>Yhml?;ZKhrBFvX$^?8yYkEs5VA=NRmZF*-)6|B32po+dzTX??}PUJUJB
zy^mb<N}77e(zv%;vM5DuuWZ^X-0GMQnxl#vHbu`+2e1V=bOEjRs-l%uXPR|>`#E73
zbZAmHU9T{yAjOpt+XU&mW%l(*?IVlX>I~MfZSVn>m=t$Q6{M3%*>FIZy_@%~n+I>5
ztmVZAX7N(a`WP2vZ@q`*l|N7Xs81`8c~?kHcp)>=qj;G;aZSZ5b%!oy-?XfkLAHN#
zimRqq+rW7~2hwKEV=C+cxxF7EGF2+^zfJW6MxT;Ge-+W(Hu~Q*&?^M{X&BLf(jW{0
zlm=l43~FGejh1D+Ea0bML~q!zWTwrRwQ185sI9E5KqLaJGn|S*5ai_K<m~M1;^Okf
zU<6oaUteE8KR<te|A2siz`(#AJ9Y#G1?}9qbJwn2AT0s72CU|`%bA&(IXO9bd3hiq
zDJ(1msR)QfVABlp5WwdZz8PTo)TvV-204BDbZ2KL05XhXkc9O2_rn>;_3PJv{PD-p
z(NW-@Z{NOs=gys<e)<V`=!Xv<PA&`BXqdw*@(?%@fxR^h<P{EkZthbg0^qzN5`mfg
zMGk-Num4-veubw05q)FYv2iwqNuyVq#){g@MC;V_`xU*-)by>YyM9b1KOfUEG}G%5
z;eSugc3uthU2lSkc`cPPTJIP)(p76TS&5~e-c&KF?&*?J&kmCR9cxp2>K^ZT{M9oJ
zrNw$%eS$ad4a$b}wg?mU`K^|>E@8;uG#++RGThXm71?+-b3%5Pk#R?jpfzNwmt{N@
zQsd~9IV?&zKh#&-u1mKWd2I`MREP^^^Xxe<*mU@`)OQ}5Gh#P0>peqRm!!g?Ms=Gr
z-0uB0KNvcLbL-T&K*B}k1XmI-Y<umV$M!zA=%&NK3hKPZHez2M+&|U8wkSM%!2Q_M
zvUg&o*rHs;seiC+=Y<OND7A!#q$ACHt4Jx>&Yx3I2G-1IwBEcmbb)aWO`x1-vxpg3
z)!__b-FC$+gs~G-MMHsX1I2a(p$$~g3u(E6W>(BKrV=$&==%kYd?m_tLXBlBHjU4V
z=m0Slts_Y)5P4AD&uAYez?vD0rKG4@zi*d-to#vU>UQ2^7#T-npFrytmEvxK+-RXY
zw3+Tw?`P83c8$v9xMRiQ13QH6N%mG(sauPUm8hxBudwLDfgAyb)@>hEa(8aQxWs`F
z{#<vuX4iwH*!g@H@#A4exwu@9yw*M?mR^mptbR~*?=d+{b)UPw`((dTQKoO5)}d={
zeJFq72nE_eEOe{G*yeR8Z8+a5Y>UcZn<g!vr>(_xlO0=5KR8K$7t|GTD@*!j%jOix
zL)YpzD|k%Ql4P;SARBqc6+s+=nmd}I+Ucr+QprZO3(4<3N3h1{&x_6UN$qHU-7mNI
zn*CQCw3L+8iiqRiPdR{KHp3>GJMLIA(ti!Dxji)qJw8V@oPMldzaDne+=RBawuT+F
z-LmYL1!8OnTFN(ETwGQp*;}@3fs<@*T7$R)m}@}m$jHd(=;)Z3nAq6Z`1tt5#Kfee
zq_niO%*;%XWW!-L9Am>tc1cMIFw`s-3vjx=z8=KYAmV6iYwPIfICJI<$f|)~266S3
zD_7v41GdTBi8Y*cz&07qI)F_Edc3U96Kjxrd=1R7U_x_4`t92#%M9arg+Pk^FL>nt
z2$26O#3IiR-{PQudCwT4D{k2Hl&6WTJxz5^9pSgKJY8qcZ+Ye%YdUgD&w{)QDW9D*
zsNiNHJKRn>*x=04=rxTmp0Cn2HLMJgm+51;q#TV5mDi3+XPtVmSc1R$rml3WMMX>S
z=39Y<ZiM4`ce1CDl{V#eT^|MQ#0l%SI)&PgjXheo&NEMncVa}MO4GB#=)Q(XyYtYP
z%KouA#=0Fw4$#AhKnxmHzun~hyK9=&np&isq_ND|g%N{Zli5q>XKwZDkh}Ds4~A~G
zH)R<kl`aIFxAvjC>rV}8ofsi>6BjSv<&0h*SyMI=n9%R1A-5ncE<*8@zudQ$B!B0J
zr?Uq~6h~iLV+f=>gepoHVj)>e$Tc;MZfE2^iXxU0Cdn*R?LeBCrXfRFj><nsXQd(_
zJc$s%CfyY78_pILG303Jn~<yMjhsO`f#kz}L@Ywh3>-$Tdi20P7~O~#M{AYl%aHP>
zrmd8bGyP1#Fhhpug3r4=BMQnT^C&+{gMscZcqd|DfvQa}<DInPD2MlVchk*#d7mhd
z&3tmo#vYn5#p4S#vv(8aS-#X>`eUae(yCQ9)Z<btft`BMuNg82A^t(65o`2_u7kO1
zBY8~6smVSz7n$aoLi;}1CQ10CZzhIZF*BxD5=P;ZjIlJltydSWB&^`pT;!>0vFhf|
ztx=tK#SM0AtbXZguK)YEnzh#noMg^^O0L{Q{FWo)KM89L%f2jq-m4@h=10SgXcZt@
z<W9B^%6j&`Vxj|i#)g<<QHBWHsCgfn>hUP#lOD9S&g)*RTPXc2P{!eKE1<l>_x`h5
zkvN068St0eDz9A@96B2s8p1vq4xIsZxpNFFD=QL-1R~}y=F5(bOW6hNmO;Mkx-1Z7
zczP}&7|av5Z{P0Y<Kyq|53F)<@L%`~C(dBP2q>J8uoQHFxH&B?Eh8f%J3IT(p`}0r
z_RPh_OZ+V@Ej@PZSY>79@#Dwg8Ded1EdcR}6Q6R7Q>RX~wY7n{;h8gM&YwSj>Cz<_
z!C+hnG78uv!-K+m_wIo~AsjSMPfx>xLJ&;=anA~W{q<Mw#QBS%GvF)`4B5X93I7sJ
z{I}lt<H!HrEd%`(oBvsTt7l$JiF$gt!ZdDC%9E@$P4&#0up~q*dFG9mryHju*(WVM
z9PY{4a@3esXX8!GDFf8eddD5DlQl+@iW2nGn-;F91&=3J)CI}^&KamaH8fv^zgn!3
zVcZ@+J2iGjZ&bB~ICg<pOer?rhYh=6+$2_QzC9+t?{@<UY>Rd`eylIQu(Z?TXv6He
z5F1z3^zS!$6<J$PC>>kxnjFt|XwPorgeS!Z_$fBfO#%|8`DUwf%k{-Ye)D{ELfvtD
z?gi6pd!Q-#!{4=pK{sw1_EkH_c{WUGv&hF6{r}N0tO-fSsXx}VC7#_(p>)Qw8%lyI
zn|wp#-x3?A*z75n+F=wSzhH<Gh3Oo0yJtQoOs%?R!W4OA5QHI6+I(2lo!Gvc=~5Ic
zhA7_GC|8&q0ZmZ~u`UDI5<gS1%KENYYp8@n8_gqIG*pw#MLX{u#&}pWLdcu^r|A)@
z@6dQk@YFP^081HUii)h`g+jca(J_;8cj)^`5tc<I$U6muGGv4kXLUzwDO!T^nxIb1
z!UmAy_lhy?5=o__^;XYult)#3En*~SLvPAc%Y)_tX+fD|9!#9Q5)$c3feM~g8A=F{
zRSrigPr2^wHEYnc@^|5rORFOrp_E95vB(mS0!!)vRPb23b5PPbTQUC`rjT+`?%Y+&
z4s?6jexY5rZN*~>W++)59jl0)o;K~is<iOcIGn!dk&N)E`!%$@x<Q|97pO0~)N_B3
zQYzl6Q$DF%T!a_YU_kurrt)eX-$BK=E6Ex7EX!`;+FB}r^6Ng-*G7U03JU)wm%j$$
zzwyEV$J~y1-MV$K5jHh71p`13Sb%UDZg{{0Kq~diXoh_;-0%SP4&aCZi~anTMt?y;
zK_MX_VD7hP&z`8LC}4#5?)^mJef#!-(I2RK033s!2X~MG=6*~j6A&4Wn2#Sn&SJ5+
zM}OQ+j^^g2ECVicfEnQV^MCQk+?5V^82IClKY}I)?2GT;Uy3l`86X^6z)cP~umE<L
zdl<-F;NbScK!2plOIndB1N{*xe*xcMDhLF?{ly>yX8ITZ^mF|Wq8hQt^W(Re<=H}}
z#z@8b$oF#D{@T-2``yj+rk1DcuCKN_$CEM>$=0=WSc;p)dy9xS;!RRHwfv*?j)^?B
zP4nIwYV^~@8K@z|%2K@}JF%><=$4anf0L>wU*Q<tbYfUOKHZ&m&UdZ8v|l}>3iT4L
z=1vP<wQF-oNW6z#{~Tk+mLfVcU3X$H%B>$CoUJJ&+XuP&SFl5?Pb<+Kr`t-9n_YDn
z2HnXrs)qeU_VF7-B||+o4df_RaWYdkDM7ZCF|QHVjUi3RXBruA3f$@JgIZ!ctr)c6
z{F6zG=){6-Uj??b#zIGL^3!8kBe7_EXJZjaK^)UI<Kl7)#Xd}ns-Q>4Mjw(V2Ve>`
z$Q4(PxSe>bhDGRF7$Kk_DGjtFd0`bsniPi4PJcr4JtAb^%(_n^qdNJscr`J7vibbV
z7#(uyDrM?>5!~P*$w@1Ys-<V@L;4yCvw|qnGqwsP5*_g9h*+N$$ADowF9<0I3Xdv~
zb*EIxN6;=0OGGhyy6B!&??y1BcMt1GThYct<)~ychIFhR6Nw<TBzYW1n5kn4scOBn
zz3yruXruoUnMtn}YO&%7me4)vn&*XQNSbA0HBabN6=^M3vH{Dz;fdFn0+a(KNH)pP
z<=fbG1LRdMf;&pe8rJfOjq}Qs1vOn2^Q9!yLKC}FdfnADy3*vbf`~txoiXaB$}{dN
z^?Q*Q9?mEkCTTO(x7GQkoj0CLevHpc-ja4~fJf<pm#R^A-MTb)0?Tuk@$&%*s~}vr
z_$sH6%_<r5gbdj*vU!He`}UC6pRx=$>{pUzadC0(q#5X+b;{fVc?FDS?tp^ZBZHD1
zca{v=cHBYoQYYiDtqgej2csFTWWeJ-*fN920-)K;%ZuA111^V#hK7ZO?ON9E-MhoX
zmz;8BWF$;#(9qboZy!7o{JfF@27raj0;vUD!GlX0Ft34q2D}DSL3ktx%6Ba-Ex<>E
zc_7@s0|P+VP51XN!5U=GSFc{Zwk&x1H$Fao`}S?_{sxGj;nbPiS;GM{KsGn5fuRPe
z#TV(k!dAnL4miGm_5UG)|3-*Ko=e|C=QC3s2uIoV30Mza+!CFAQu8gBqYQ&?{qC7a
zcB84;E~NbIqz&H3Lbfp8u%q#&^u7dD3gTLxP`&%H3$1=TwXEzmv}cEHs6OJh`#^t_
zYEw{1S!Z}!F(lzsTGVFo{p|>{R@wS3jpk6Kvi}S58ihoIz6;Z$8nqFg{zb&2)vc=T
zhs_T?&2dCEZ`n{7u_5K~_U!Jb-nysT3Y?{m8@k3%Mmh95t;?9Dbruwf*B{eE%8t!v
zo;Y;$n3$P;o8wJ2CyTpEo8}!UKKkh=-Er+Mhgq8279qQhQBIC@`Se4jLCyUmCc82s
zO%)<WMx^@d<|5Zd(R<&J79u26NEVm^HBx6%i5usRks>)Bl{VnPd)<J6M{hO2?!{Ib
zGJ@8;-XXpQE9#$0w>IYt)4iD*bZKJ2D|8A`dWyaNhHzgtsSuSml*f-;@FSfR4k*rH
zp<FUanJCI|-sLEL;%ei0eX>}lDRz*)@qQ4LN_;&XKuI+<3#1gI1cpknErwXdP^YMI
zd;eZD42HPMr=Bc>oEfS_hiNipk~YoT#;+fzKP5S%#wXY)-N@s5`FbjLyVlo2&xjGI
z@vbAslvGe_NUK(DZa^%UV#S#3kbG>qy;)n;3Eqm72D-zD^$E+uLgVR^_a9C&gv5ED
zUk`E+j-c)Dpze`$H5`GEE&V#h<LC(YDPgUxR!u0Gh|y_sZGHpVExV{Gh2P5}wJDci
zl_zlUL_bQ2?M!%~WrttqKZ9x=4rdGDG*PORZG3&a*{a#ECaJQg@qf5xDd(?&c}0_s
z8?pbIxLq`Ns0@aGIyyRfdVf~uR+Q#IV-8ICfKOhLUx57B&dzQ{Z1MS!4`3Js#-JRx
zbLY<8yO->7baZrVY;0WIC*s0p7*ydhGc(~H91PtfM~-l};Q(mi`UR*vgRV1Z#&Op#
z;EG&(`==ovz%4gxSJa)aU0d?Tqobo^V@n;2iHRkL42sSxx^i&M;`7)TCN9V?R!s7M
zAAa*@so4ydn?Lu!Fl%AN!h%`*H~QT4!ulIt{;vtK$kX@@U^ZLy<fpBXTNn5W6*(R3
zF_>5R%F@GUDbCO+YYv{CtL+UqSRQ`PrX$E+!tu?AxjQ4{GP2Za>h)xG$(yMufwZ4x
z#?#9^+=PAOyE{V}3Ps&c&;7cae`0GadQpBmvkTI5wOVX)ib>7aO_XxkV7!{6LWyH{
z=x_ADIpAV@?j&Me_T(w@cK7g0P}8G!l`YOyHn>rja@j2f)8lU`T0+_^ExoIIh+0K?
zNi~<fnFdaxjXMpUn!*^?b$N}0G7CJ<rkWm53(DsZs<Q<--fZNm=MfVH4T)Nd?M^-&
zY4!QFQBt+5a+H2%D~r8rW^@OYWS#bv%G(p23t{hBkuxC6RDsnZ)HKP_y_DCiVnwPM
z2BSm3y7V}Ou&5lwO$%}+w7Wqy{{UrtRe>b6Pi;zN)Jla6ZOAmGqbX@4;-p5YfV5oR
z2p_DvD8-kdVD6-x0p*ycW<r@*k4$L4>3jxs{dH^>6lNMVaG0X^$O;NFomYU?51SI9
zNYhkb=ma({8xl1kr$Yr;n=B~Rv?BvLh&}HQWt%cHA+<Rg0!gu-LTkLiX`)s4g+`FW
zYRnmm5Y^Ok#4vgjClv|~wjC{|BoeeJwFx#HR6yl>x_=`D^DKnSC!tC@c6t+kb`v?d
zK%49;<<hPC!v>>aU;RQ?9a4=%QE$jqHGVR~nS7D1{Jod}W#5Dne+I+CeN30=L90!{
zKa!chTJK~wO1r?{X86+81~rBJo|EK6@*waRQ1H(pol>{hT=_jmiWE^KaG8T=KX45;
zOt6(UqKvEb@V2z~ucm6|v{{}!_d48uT|%>-O02&@YO31V7jswj2XubBX0-Qf>=qId
zl9iQ(Q;&a**Fc|>nmd8k(fQM0!<#E`%mEMkU}VGonX)Wy7Yzy;U&J?9TiL#SyRWY=
z$e3aO47cZEVq)OYUP{VRguz|P$jJChhYl{#<>loaI&=tbY7`X}!D}n9X9lVB7n>R<
zPyQu!?(FR3?m1t+ykwX`T@JLJ`}+F8<_g@HyLRnU^ZCage;gV4b0B!<&K)?D=H6U+
z`gAFc1|jq>zx?vKXNE%!ILqKpF+c*nB7XiGxBSlvhWOv;KMirjB2U*hu-oFz6bEs*
zY(o-0%okVqsLqrY7_`)|vzOhj1no2%m8jM=3q{J?-W0%*&E*AqeoJX^t|y{YT}*=p
zZk}++!dKXaJ7Jo=D>eo9;mXTegR;h2nH&jbM|j%){F{Z?f>!OybFamR#B~efM>NbZ
zu33$T%ly4(k^`Mu)4Fu}+TzCtP8_N&+f>`NL%r8KA!XCnDG%ksdY!3}CaKUhdG-D#
z?Qyey4627+<?IkG_@J{|tBF(fR7bCR?uPQRcN%@c%$&sXZL97)HyX8V-o8elT}DNF
zn3%E1;E=F=uZt_)(I3~f9gSgy*3A*LsyFt&BYAi^$e_NzZY5@B#YpgaXT`)~77U5|
zf48Sx7a>)QtR`omRjD4l6;@2fnypq06{!?IVaVCCri8A|be+gDdHIU2O}3C!GW9{>
zx=E$Y>y=HD(E{0|Ty)ydp(6=T(ff&M?T^&SFDFMrNsaCj#^l;bx&-DI4K&nd%&4Zz
z(uT%0$X$~oa^wn0B{lNv$$UK7rrj@}BtJEhNm^*DmLs=~qf1Hl>#N0rH!-^DYp>nD
zDnzP%OBcJ9mWjwh&scG!3A?PYNoyL%y6Ef8T_+?j-!V!_6csjQC=m7&1(8+8W&{kL
zd?`uG?bK}yXBTFaEuG+*vS-joY|@H@APH`S^x47`zP4eiQKmC3Q(Bj_Jt8iQ%ruWq
z)BR~v4S^AoesN@rLq$II;A16n4#w>~eIWIvQfl@A^3PXxUE<H<{neTJ>KJ|PR`Z#<
zE5fUMiPaC4%IRvqjvA%+tnMOOql}}82Bb?b+Iqxfzw_TRsB$!PZb+lPd2U$eY{J*3
zhj=_5<~6Ku2RAU+aA`wB;|t?LZdk)n2r$~*Jvul6SrJQv#s*kTv9`9}ym_;|{nC`s
z+1c6E)fIG`0i!|oyko~FXASBXv9YlU2?^ZmCEQR3eP)1TxYEoWXTV%8FE2lS{5YG<
z1`|N;Rg?Dib};w@HMvWdE`icAs4Ii6^6>C5+?2a{^Cl?E-MV!PHqk%-yc9NrwG$9I
z1O9Sn7B61B0PuzRyCSLp;$|^7g<*X@5d4~n{u`byF8;@n`kxkJktg{ZkiBu6ZH={q
zjK1$k2a9jem6lii)F>i($!;T@<fQqY&XDOL`MFnYI)d`opG+{}H(qYs`7o_&u>=i6
z2Udt~6UkvbB`G{q{`V*v>(rIIvG}XSLBbnst&M{>OV$e*td(5A|DKGHGKod!wKFq+
zlu_F_s3g45R+!tOReo(%-zz#6yJes0tklj-S8*>r3M!yKbwZB4-ZlAx53)A<Ff+)w
z{j~@Kx6iO!`aRRG--=NACaA+;<PlqT-uuJrDC^n0f+X|n1s&}2dQ*I&+{~lkvh0(Q
zi`cX-jhbp4C4gQLTi1|vdhI{_H9{Wma4q+1qH5kviRd+=jJH-g7Ua`8GGfvYLpCT5
z!ir&%+a(+<ss!xRG||SyXwgoHDI>vqS_X=-WRjtR)DmmR(LB{Q-1H+_N&if_B~xUL
zfD==49j~%1#HW{P4TWDv;GrzjX)3gT-rX7+Hs&Zm$F9G%hIX6wQXswS2;oFgGaBP4
z;jR-yLX-tDiUKO1(dF#k+@?W0OgpTh==la~XpbqA20gg`lmHztO|^yW<~0Pe*YsI3
zkrp>BnXAzT5Q8Pb+pR=CgTYZVw-h2EwcjR<dbEOAQyLD0@Td9c1_~?LReFuBk>CNV
z^wh~@G0NPk65Fh-BS@x=KLt`pPl>D6VN{=!i==1_(W19e)~iitEbGWDF=MjMRjnZB
z0&IY<A4GCzGi6lqXnyJ*%c35<t*#h<f@IjL@huik{yFC;E~9HHM^&gC(et7o&`MA4
z`md@(IvQnm8MCA#C{-T@`I;oR)T9f~$0R_VN?geP!)_D4HeeJM7A6n~{{poCUMr9e
zjQHnGX|OX6x9(u`y<*#N#j4>7NB-aTKtL4)j<(^2Ls!>7XOCd19C&n)tiU#X*REY~
z2?Q?TfsrHFIZR#_?ApPnAuH?CcL8|xf`X+z7w%>ryzm0H5aE8v3bWqW*a&y^PA{vo
z^V5<Gyph<`v(%;@92^93Hdsf5_sYR%0xnd8Zv+UnVRsIXB3G2C;l>EaT~?I#xbtrC
zy?_NLF|a@+1E(@8x+U<E%ZiE!w~2?pFWlX}6@Q;w|1J3ui#*T20o`UXj{<Cu)R5vo
zSkCdE*fN-RQYsB3CF<#^aVjrndOf2KEfqfkg>`eH-i0Er-N<6oS?NK1p_;_tWOwq<
zNE}sZm#Z)v+1;tbP%iJDI9;=pl*AN=RR4BHZ&ZJr`2K?LM1P5;_V(y4F<Q>b=EQ4G
zjkB07*pn1&MTo|%J?bRMJ^IDtfaiy(-UMG|e!u7W0dg~Bkx^?;icbukEO5F2;z%4b
zEF8mh(y7jb8zB=Ko3j{>QYS3)3Y#Cb>(Y~hP9rqmpE`WE&XU52xRb{=@?1=7POEMZ
zxh4Kg%*ATBzCn!gyIq3Xu^{}3tn7Ag6yyZS-%gz&Ls2w-Ds;HP#Rk$RlTitz^ZHxe
ze^#L~_N_5<V#tOFPg_G7mc10HBFg<o4NBUo(E!pjZ?>h*eNGkW4*IRHMIhECht!Cr
z4CO6ij6F!D*rJJ?I&7L3Nk*(_IIkHSokco;rCiH9i+?(Tr?B4m8sax_Xwa#9gPWlg
zQ?d<Idr!j_Dl}!NK&qF|sX<ky^J-A;JsJ&aH|?;60`AcSA;LKheSMh7Oi#|;5hDf~
zon}>+RfaVdB<EmfGPA<pA|P$bRv-2nGV7HcnM7-xsHi24N)+HJnwW`oCPC7p>kRHQ
zA38-~aot5?E;bC*)TLCLaB{5`#y}VA!r!J?I`)*V>t&5mqz7X>tuMTCWhzP7IFqv<
z?3%xpL?N((C=E6GOzB@EJA_Y@(+Z(z6EoQ{<=0_RRQqt9fM)x^aFskvd}?31{kEE8
zkR#SphLW^?7t>z;Zr~rQ;oNR}8QorAv)rE-_Wnm&)WC#;Yac6c{l#G<Jc;CP>#Z>1
z-+i|fc!Pr<-0O#Ma}Rdoa9bLb^j4Ilxf^;b7Q#V28ie0q6&%DUpqrPOxkO`-oBYjg
z3kXd>E2O%*8r}xy9xDR&0`LNV4#tYGKfiHfsi6n25W-Q&idAs#gc}aIxet19uZ4e(
zS(vT>s&C)Eg?|Ba753+F+6^;y1wa1=p`Yu&h3fx;zA@q67-={!bXacv-uKutY~dqS
z6DR-WUGW^XK(zEuvj|DNhVemUZ6n`x<x0`5<L3=Jf^RBC#Skd&s~u4F?#CAOjXLov
zN*+BXwZum4&W$R)O}LO0^q7;&#IVThq{CGPrB`cmD&3K7f7Vd<{pq^w)op$;@u5X3
zClI>UlJ}9RwPaVV)(!W4Q#$*k9WZX=KFQHY>+Mdhb-^wMOv&bOBblBlMn5Yz&B<sX
zT-Hya-kuni;9sqoYbUv7tGunMyS1ZrnZo&i2>Y9=9E-ZK(I|&LJLU96K^t5<6w5Mc
z-+a$-o=>H6U)kLvjoF?NL+6XAaf0#GZkEwGLl-SwRBZ+sLd|4&grfS$<i^{Q9Fah1
zT{e=K+=^V9L{if@<pxYVI>3OTjNfF)ltN1ruxs#0@<BSuGHRRs_Fh_E7BO*ZM3pqo
zdUU`FH<eEyNlRBNkv>k+<w+jw>TKf6$$VK-0ozZFbbSCVPkJZir$ri?9FZq&Wh)z#
z+9v4<L>soBcEE=D0s5h*C4-rw!iHG<2DT={Lj*F!N^RIr;2_WulAJXg=I~4m`k6Yj
z3}<M_kU%dOFlF#%1`I`Xgdqbt9UXvjwkpV%CbxJC&!l@)BKv~Lk*M*MB-*%B4?USS
zJsfFNtDmvf*+ZSVde#bSZSRC%Kd!odV)`+;N6DKnYin0ZFWs2qR3PWqIx((inRnqa
zx!--1A@<xBYkPrYAhwpAG05RG|HA~E+u#s9dWv+&8z<1tx4!pL%_+5ObTy^#HOhNL
z^DrE7U#ENd8d6I7yz(;E{6f4Wvcw#zwPEzdkPd9Qjc$ehcAZb?E4M!Q`1oXHW&hqp
z|2u*m_un+H;X@zbV$<iQ+uZ!qz!Hv6V19oQ-e5No+--teecUB$I0J`odceIAaKi~4
zaDoqdfNLJ`9yy>m9DswuHSDb6h99`>QCV3DMwr}fYtZTgEfKK!a{m0EGfQ|o9L~B|
zEWB`Y9aKcV*t}j5bgyXj!DpFP3@?FTw8K+OZp#eo^B9I3^W3Cg0sGI1{zd&qDUMj=
z>G=lVZBg7_Gx%e<MY>iga%fbXWej<uN^Fii&c?e5PWqY^+*VXt;`b17Gr`wf_E5IC
zUxRbK?&SpA?EJfxILFf)><xsZT~aE`g5;lL2dhtwysE)pjnR;FGB=YC-t49!>OjzK
z!^<jPJx(j55yUCa(8o=PgEy``$8b<BTHR!Jf2&xTtmh8@*-RvDLLtVBQWMi)Jt1|}
z<QLmHqh8rSTvngKYle_+-j2$tBxaj9vwpi_pv)v^SnH5~rD6BE-r(ZHKIQk(TcP}$
zYBbiyFlUTg9?Pm>@!$ttELA@{Dm}fmR~+lgO3;{<vQ3|@WD(U&WIXvKTte!EJ2S~!
zPzwWYlTQs8I7|z13tiXWx=2?aHS>TJH<D3h8H!3nkX=!ochbvIeLs^nA!>Wm`Ftc8
z;%hMLu(CSHnl!q4>^NGLw93gZh4gv?jVIa6`KbzSVD{6`jt5<(?=v;(r+=5OTuqNP
z<*?{a#u3-)F{aE)dIQ6ZBPSZ5fz|kq?aL;Sk<qSshfvWOr2M3J$_gaoDYU^(f#|-&
z+fmWwBwqns&tY>!Cm%Ts(bsJ&JE6(Mp$WvBC?6FiLP{DEH$yu4!Mk6X`SIM<ea59D
z{zX;d9}O6itxo(`_Y%#Vo|Ao1wLK@r?57y=SBPZWB&*3*=Qfd1r&Q-G{h!c=)569L
z9jl>(Y0f&SDJ?`Fi|8k3g$(?xt4JQJM9vGaA_o#2A(ryRNrsr<g<FZkC&m1veO6!M
z?_;@0O7iNU3mwkWGrI)Ozh$3ANg4&%Q_zJWz5JTQBbe2GUMG<@Qd4cjlH5O2?sDiW
zJ2HHHd?F$ua&mJ24wt_u;JRVI4V*Tda>FO}0HA@p=Dz7cCNE{$Ha1H}8r)X`$CaF&
zow<#)xA#)M@p-rb+Ix|aO9%#+mEa{8a7HgFX({43c<?V>5_nG>+*AUqhG5SHZdI>X
za%pY-bWjN#>j2}%ix)5c@WT(_EFMf-u;g;<)~%m@`f0^h;p4}DE)#N36S*M^gLQ>q
z!UDDen0}F@u(;=oa2BzGnEz2${vQ!yktg_Dkge4&{^XjZVZ<w*Ic#Kg-A14MUi?zq
zPtN_Re~8WN8--|-gUDK8UYeY%xhzr3c?sEuy_U9RBYD~N?iEdRQm40)l1I;mg~@tt
z-4nhyk+?}!#+XjXBjn47#(<&@ld76gWetw1Rl=-)7a?YVarjh~zle|j4dwK--tPy?
z4nhNN_N`-hKWQbc_3K;j(tAgRC$x5Hrr({L3|><sebam^q^?{!<nV*Pihp-7iYSYl
z2}k;_q2ezm?@8_Gd4_V37&mph|JrFyy|dPw!uxJIjiy(=SX>d*rzu~#x7@SzY2(=u
zHO3^;m}R4MjHM&`8!Eu>sdUyR6eZmw3Kc*i??+LHiCf#mD(Oj<M*Vb`64iW3Qr9Xd
z5ba6ZOdtiU!uVK?KV(_<2rTrFSRE1^G4-*zRJyWx#{k{8*S(*fXqwtj-`krxK#w%V
z_S28`A_nQZP08tWLNDzqp42%xB28k6DQ6NR#`8moW@3IyB-SKdmIUpXR9ZBP8o;<F
zwNd30Go}3$NqvM`%Fb8kc2F3W!ap4sGnQnEraJg%=9qLH4I+D?aQ)TjxX2><0s4FT
zapJqGMpEfD6~>X{hS)y3R***%G1|1RuZfA2p7W+_>F{b3v#vd4R-TpVpgEIpqbIBm
zr=~Zx^oXWrpd39)b#zPbEikKlh+_RncX<Pi=JX(l=t^#A4<YOdhxkQuH{nv7Y85b&
z=*AW;iYGoC^J`N<dH|o|kV9q>VteK>oic|@57Wwe&S){vk%$k?mg~h{5i#32uW$DV
z7${jQNqsucG&iXGwM2VGBm0ZM{TqFrT*Foxlt;L85cmw!iW^MaBgqvfP(c*(we>|n
zaPA{{D@rBY+vMOVD%fL$kD_u<E<r624oARgRIs}UPNRPDNYfXC60kc1j-pmn{3Wff
zsrdwY?wlIlm$`WHB7A)b<kaBY()H`t;j^dU+!B~#!o$nS$uD=Vzm}2!*29Qj(Yaoc
zkGy~X9{6Gy<pAy<K70Vg2Lhh~_;*(P6QKVifCfS=@>G8VrZ-LubAs|K%@PH5wQz-x
zw8(+!QYAL0)kJh+WX40x3~p6v$cG^1H~dB_rkcyMFNhy(aIW9<rrEaa)sI>A?iDA|
z<WBE8HIJSRoy4Z+x~7V~P4dIO^f6c4iDCKdx_~BS-;4%GrSYbgSB2cicikji>HOLh
z|GbF$7eq$dN&Y@$|04}K_k)OGTpL5-nq$|CyA9eWZ=!akxHKCJ?mU_oSvi)(e2vbj
zKVWFv{?G`~lvf#Ks%tRot1atrxMo)BUT{&)_HwP@g*LNMOWZoMA_H8Fx)eEU(OKou
zJmyD|!&SQkTuP8^lV|s%!oiH7HRCo=Cw|!2caoJEh09YQ+h7V}NZ0i}-5NXAbJWnn
zM5Y`sl5>|HXBpK`mq>E*F+w2w1juxhOK%46j6N2#<{57{eS4LZy#y(-Du={G_hsb9
zppCOgwI#jj63(x}Q{puAh*-Sgwp1h(V-$zkO?*iRpqxXG4-{c5+EtE<$8{@~h#3<Z
z1b=F(5L8qnjL9P=hgTmHO?DN>Ms4U7h61PW<jeTIZ=~yMqtmjgkT#}F(M+|}t0JBz
z*=SiZOaFL{z)LDqwnPGZ?Eo>F?p4qGvu~R7uLUyp+76a!+w0S7(UTmZ;)dZ&l;h7<
zv)a=Z0(~iYmkWhr9T%=RRacL&h_2*vVx8*YaItGz6|}-kne4Qp-bnv#25$EDQPzQH
z_IOnF*9KoUl@z625FS43|AJXx8WLAUIhMk!<SHDJprk41=t1w@HfI|zthBEeBP5@|
z<20!BL!;L6e);P@sRLgt_Q6BOe>tgM!R8e)HTPaQe1hq3wirRf4`dwRI39c%54O`_
zTakNJ5k5l=pIU-jeIV2b4PBZT?%usr9RbTQU~?SY$2)LfDb#>>#^E>`PBFj%YB<6G
zaWs5n2|nHd?<Ru#q#(rrH}1gM4sc%S>eVIC!nNxa=at}5AiSFh+%>nahJ$FJFYb&3
z)_!8_A2+4{CWr=uJlIZuOaA<Sin9NLzOf}@;j0w2L!@+l91=nf8DQnrH;}PQkq1)Q
zYgXUa%%G@3LpcN~Z+lv(oMA3&(w?$Z>@(=4*=FnGq|uJ2H(0c#hgfYg>&TAY;9uZI
zTiRu`RkGKu%nod>*l0v!=VyE8&b+R@USXB*X+xZUBErP^Im*3{rR|=O_CgsnFFeV<
z)@&#jtuw8(Q`CGn!Pk(~v#u_$($^eEsB`Ia^VT!}m~2#CRBtU4@S^&z=F!}^%8*I-
ztSsj>*?PNX?V`>NXgK6}y_n*hA7EIPPc)VwU(=rvv={H-H$1{pOhS!M%he<YNoV_H
zw6n_6g7UJuG9DwYpBCT4N}#3kLtEnngdhg3CR5sq(?JYl)E0Kr@v+2F<(+bq$&%Yd
z7!T+It{p>a@<<~DWXC*Lu;zVZ14fb*>|L27DR0Wb8*Q5xg7!=YjjE9&p88S90R}1*
z7Ju9{x$xZC8gefBtv@;Om8mK@0KG6!v_@K;BXlz24t+HVLkJ{gqZj&0QE^sGg-|Vy
zJXF(LIwC_>^-(8wax6It`xI&fA#?r^I>B8;AcMtU5XlFvO&to-;uGuCsJ>F}l<I8l
zjm%XinsyRZN(AQ`oMZHn*%BW1K0&%Edzo*TXo(9n1pb4n5kk^1D6KbFj|e5LreAq2
zq*U0!JA~hRZZb{O&y3KRU7=zt3As!+vGZ;r`^F27#cVl(nSF=II%mWh=|AF;y)gwh
zqhQwCDJh4s*6>wo?-&0si&~QOVJXi+`KWWr=~6+x1=jc+&g?Vz@W<CejepZE!vbht
z^2*Cvfo8b92Oq@)T!xQ*z|9Y^$OxkuHqR?MAMkZNTU%Su<bz8dpxyzx9V^a!czb*M
z`24v(1K<1DxpRryU%N03*35wt{}6m_8k}N+(-3&s9Nw$}uL!J2qnCDR{&+b6%opJ|
z1K{CeS65eW@6zp$;o+sR;@H^IZX!5K%AGyK9S%_P15J(<Gexjq4mYpit4k{;iz__y
z=V3Dtw=4b}<v_5n{uccUJpCULVv(oqTimkR#hOgC;)cCE(!OM^>3;vbyG91fn=*zC
z*G8uc*(&ST??TFZt~<yib;X-}C=x=9);mT#ZLBq#R8pj$-jo<2<mQr6+_ZEok5+r?
zHnw0XhZeSU`RE_C+3jhO8zG~OaDsPpIVpd`!u#pWk@B^)L1fqSw&L5(DrfMseP}1i
zHJ(T4FKMdU*%qxzF(D*zE#`K)XO`FX1zq~wcC`oEl(mg|u~j7kyykhej`~Y&Y>ACO
zI7Wr+s{}9H-Z|(@sNW!ZWDeUOnB6tkj2k`}G2m%kxj5W)?%D8lbiY;8`{awNREklE
z*sM)3A7mi2z0Jls`qvSWma3xTD6+6p6*U!Y+?OmPpvpk3GK_^}$s$UVlzenwUz+5C
zJ{BYA>EBN$q`E^2<Y?b1au&L8Fk5UZ5v!<AWg?+y4g#Y=e#Ux4GF&?RA>u;hB#>RH
zSS1@n|I8eFJ)<dlDY=m@LVE2TK(s;{_ZK6&tQZ7unHpPRa-&nARU}%qw~YT%3NI9U
z#cYI0yriE~aa(SVQ4tg4<XO3|``u>Dfx8}?u|Gm*9&3`#Dc!2!t|@#Bf7ec$&Z#Z~
zUdFbBaW*n6R18`h-;wXKrd(^m7L{cZ*V?Cy`q-W+eJP-iPi=da6=$_=Zb61p$-^;W
zmGr@gG^9Un($gi6L%Ah>Yr!Z-bgaEkAk3w)xW#pYf3~Y`WWxKGbT!UoQEFAL&x7-V
z{&;GXn(Lh4nz7<=0X!QmY$L}&BZ`pr9c`J_TMD}~2}$>?gcMVCh?>W-ZcA=?M{@*u
z)v7NaG6jYiM(o$bjb3i<u2>@nmU!*jzZ@q0TJ>&;z{`jJ!PzBH;eewH*e9>J><^#y
zXD}EZ9v)k_uFRalZKa*df)~iYxIGSgW^nKhgwNorM{e$4R$l;dLAefoy#>C!R9^n)
zwNma~#>U3KERDm_G*}%60rlClpI{6h{pVhQSrJx)o28#OvEl7;?z11<kcJy~FrHz{
zz2ZFp7|*a)q#E#1JdjubHm{if{r|;me{07>O?*7lvCJ@>N0VKnHQn#JulRO@{!*<P
zU(8uG6Y1M%MpRku<{i?jjW_YC6(Ek*JFZKLsKxrUnkrX?B-*m=Pd4%6?%cM0)0k)L
zeI!xy>X`;XqU}D_;LS&aBz*~PA2qfpUy~;>cVYO(5hG>l>cfIQ3s6yRi+YE72u<@w
zUdwm!iE@17sj}D6Sw9D}7HI8<cVz~?oon*dTPIo-xk~d%-RjNKA_0jTgla0vEa+hz
zqca+*Hu!&F-wnSWzt6V3vde4XT!Z2>8RJroVWPyF+{1c{*pG7-kIL9myNZ4&$uWW~
zpRb+Lyr6Qq&s!z4?(BAh&7&3{pbi>D;Wvd0wa@`6$>L!UHVCx|OM^<uh|wgnuJ0tN
z7~40HE_&VxD~mVb-wf?y4${>rldLIXK2~)w2kB$VOgB<uSVQ^KnfX{s^wc9OVNrqZ
zV3HWq2C`G}7?G#knN+c|z(x-<c}1KUqV7&4I}u9f=u85#X??BQMG?vr^W-%;Q#epf
z8ijVOT~$bEjbi4Y17FTz<oF)aS=0uG2SczVp*P)L0b5w0s%MGIWRpDza+LE`tW^wm
zx+ha8?1K^pzc#3oFI9F`6Ha7LRUcY4&G%em5$ae%sx3O8@fE-9ej{F6!&ZhbLc#k5
zy?BjoG8QEnZM@|O=Gu8ILh#0!G=V}}8>g+E^dbGhz3t6y@@Gi_-s#7mUy2k&Oiyr9
zH3aV~A+T~r+9av0UFTL4Tw5;|Aw5l}{0y7uYy>_$-=|+LMc-H0Z!3J1N$6_{^O`kl
zxU-CZInMZ^;ItVY2d;QzOJ84~o7%uRb6=E(Ei{-Eg2i)i3*>W-Q>mXI4w7dF2L}jR
z!uRKA^0sYTx-JbG9N<hT_X7a%b-cJ`aYxNye3+J&20y+94t{X=p}~P^KyA1f4OY?N
z6~-^(8*DMc_y+wv_}MM6#R%7<!Tt=qst7LVfmOv7{k)l(KP%DCpRc^^58LK1p7;1X
z0fD*vxpV%afBt*V{6AuS|7CsSJ1u60SLZb_RU7y4o3-=Lx(w!(zVh-|K9(mG%S`4x
zpXv3<DSs$uYa}2+YELkEQEM$UTJPxhkX38MQYI`Nm}X^!j3?JsE_vp`x>LLdRS8!m
z>V?TcEn&#fGrO)ETKv!*=SfB>6`$OdSQdqFz=`=6<-d;)ZMiNHWNyG*Xt=hz#lk)D
z=hWD=<6AJ>+n|D?;kHwIFH|*^AVm-jAydBPQSY89-I1io$AwJk{E~etMm%rsC$?rY
zSrK<iqe&1_sz2(;A@6CxKf^vpSFDoR*Sp0$b5V3o?=^Z<ds-FwgH89&_>6;@_xJz$
z3OlMhuc#+|!Pk8;Jrq6j(*dLfk)y(|=bx1ztfhj4l8j2|Ysp;%0ud`3FqtN35hR}>
zxV2C&Q$X63qpBi?ML^XxrumZOnHgUcY2hK;Qc0B3pEvQn6+?NwsJMcIipq=<o(x6!
zt-T@AnL)iob1G7x)D{+~Qk!|Q9V_xp7@?Bqozm^e=lBb?EYY1QffBlfd=4(;Tci5+
z=AAy2o#sX9)+cGNnX6m9I<k(EDkhIp%kkD&6=^HpBJq0vc^Z;_zHM~dfjNvFrS1v>
zCn4`s;DR|si(0KXZV{cjPiB;z>YCw%owFfTRCT4eYG=+BX!+(vlO!Uf471cPoa4<_
zP0xk+Jt*N~PWn4;tA^Qci~5I{Ql|Z831oIvz|gU~feU2y_BP&()svzEy$L@I6<Qye
ztmDbhyT2edBxQ8Bh+px<^xkVKJ4}m;R=Yhc5YDI}w+qRl9;~7%9ro`=9fm#gS1K9;
z0s;~e67WLH7Xkav>hnr8NHc(w1{X^|Z$HD-1qc1X<{`W?4quE0x1zy}4^Elg+?KY+
z;rpZjygoilXYu^~m)HwlJ>0WrPh4Ca_bJlU)YOcO3^<Db*GRcB3l<T<79xBT8oqi5
zKdAzrB3*&g75DDIAP+oY2qI>9DG_X&a6=WoNecWfFuQON0Xto8eQL!0@n9j~>Zc6i
zGks#~U*E-Ap-<Vwvi>hK_FofXktge0(u`e1&W;<>`a9<};&Fx5df&N)NG(Oo8Pc|c
zDj^<FK32l`AhII((Xc|Xx#EQMaFhnh_)MV*N42_fyllfPEdcSj=~gL;C(Iu4tmoD#
zpMIr;$T!+pmyp%Jl*XlGNt*1#Hu}!1A1YDTxGdU;J?dR%bwT3p`<{S-ahZ*)H**#O
z(_N&Fs5mlQ)byRz$}~5;A7$D-&0>}7e|+_1-e1QqLM2AN0D052L%C_n0Bg;;w?@2F
z>b;;T*?<)M!Q=*-k<AJ(*@#-l$(m0;=72Cxp1(l6P5f;!cKhItr$w)k(IRU+`7RO9
z5G~GIzYToKdMC76<mZDAyp^AR#9@c&bpo_}1hHaAmF0z4JXT^gvUiXkNT%5-l7ew8
zSrW$A2S>t45a?D}-UK{J*9TJeZ@{=okU9+tRClV3Ca+QpH7wROERwP%?=wyIw%?xH
z*-v+*MNP+SP{Scnq)|eFt`TZHjlmHX;PVn2S7#zpUXce8q!z0JT~pLdw=%&+tcSiq
zM;(&06X*)CK7;RK%1b;ed`zbr4UOWWeN_hYiIY`>)TX3CMu}9M83WnkQ-j!Tx3#gL
znv@ovRwb4=*B4K+>KfH1yVnwBPFrw>rSrAji^zLrTzq?7Hs*HGNO>htt;@#2C!^YQ
zgloEDs=n`#c9Di5LSf6>S2#>T2gha0ZNccYa{N$o(3$N8SDv`08?E}q&G??An_@|&
zHU|}$$u89kJ@-)0e&hFDE&ffMRTSYn?RWu&TUo>baTW8JG{u%fJ65Zty})EhZ!b5n
zR?JKanf&xR%RafU+{{x_Qd(i2zviFeX&?7yICsDS9xYsvXsp;sfl~~SB!gE9!6gqW
zmCBtS!{ue}YyE)3@N!^a;9r`{;Nh#-*w}sh_Q8X_FP<KS&)Suimco$)+>@)T`vhUo
zpx~}2!$<76ukyq1R}Bsha=#!8pX3Mlg`bZFRSGzO0O2smgg?(3R&0aAS;LCX+zQ@u
zH;%sru>Y+7H6;FD6Jn7k@mmmGsFgZkFQx5zA=k#)nn63f3q4qFvxgsJkiIPhOFpfB
zUC8oo{#abZ!5^y)w*<A3nNuP)beGm(EsQYd1d$&~%$%8Z5e+<J68dOimQTl5lFB|k
zfUc+RpDpq5<s;ou(YtADKF^<eOR-?yA;G`4XIz0Kuvy0+H!I$UeNvJ%D>&C{sh_|9
zsh@{$wE}HB|1T(mBd8iW%^J&?3d*!?A`F*?8*L8F^gLAeFzR=o+-mRmjVgPX8L!gW
zPH(SWnSa}Ta?_;g_MoRvA3Z~Ee|s%?tB+q9&U<n1cT$fVDqjmt$`u{FkFTr_zAHKX
z%sDq9r6}ZqV#$Li3m<=dM@}P`&reb%lMrY6A%9a9gqTIE<uSWA&&K<pXj9H0WQ};-
z3+0&mA9U146d!a{vgGta<ykWWkfkN|h~xH}PPU_urN529raSmz4fVIFR?3){;T)MQ
zMNe+75lfC>pcoV)Z#aK@I9A3!@##%|vTYR0Q60-Zq9`p_#7`-$3TKFvrgmh;Y$_O;
zA&<QC=cgT)ientY1<d^fQD03IU}%#`1&ZnNX(R_37bSit@$w9e40NYpR54s(ypOus
za)C!I!+pA6h_2g(Q8)5h=)xVQk*(3iZUP0jshLk779k*&v3%8?NijttVew2GOn{Gf
zkJt{*r1jCm521cBqgPMIk(u9<DIT|rYxxM|A|J+?hc}Fpv2hsH!RpDteZ2dEFZl#q
zpcl|7CsG1<`;Wi65ydNUJzCHjz2?dZUda-~(Lou9S5>PRvJb1sQgO_U6&eReBGM&X
zQzr!#(yv{30N$ciIr`!oHw^z-+q^=si`LZCgt@(92*~ZCx&8A}Y_a@&I2=HOH5Bl`
zERFW(5D;V++;{V~Y}o?elnx9G1P7%-KLgw=h0jWZ>v`~VvPnswLg)1KrR#az56QwO
zm%z(~;8N-5+k^0RQuxdVJmZ5O!~$nNu3TBlD7c>nhj0DE8z<aHNgqCZ_~_9i_^1aw
z?Bhl{Y^~ukG;E|-kQ^3_^#9l1m4-EW=UYUOfGkNMtYJ$CgjLX>LDT|SNWzvE5*9^e
z2^0}6q-b40B`jf+NkW1MQrQ9_f(nAgt;4E-SPcy-)=oviBGncY+L<oi`{+wwGpN1J
z+?jjlgTK%J&*wSM`JV+mRoffni(8~u{4tFGr@`;LAMCkW?mGZANk1j*Lncm$*DP`G
zTA5Sglofj5ZdaRqfJMCNpmJ)kA*76gd~(tB(YGN6vB7%M@&OrOaj=YGG@;yP-UR=J
zwd;=N2~u>?j9^<_{PrMx$3-fc^^iY?ty40{%J>s-DcAO<-_qJ?$wj1IjjTc|wU0tL
zR+7Xq#YioKGfyY~db$+en2i6RIK4+!I9OrvH0_Rkd#@XH>!H;xf>E8i)W}tV&j>A#
zbE?<=mr?pATeG@|{n&JlxL_t{)qyXFCGbK;;IQ839z))1eSN6E5CgO0_z1_XH=p0#
zJ1G$;dh`^gPbNAZUH8%-GnG!DM2a=~4@`GWunklB>84WJ{?lG!K9IL(*S(NK#0fr-
zzDN3(kWwP~ZU}o1yFbL%$EY`C%^u0Iko7(jIe?JH&If{gCh~wpT7NFE(<dh%aJbiD
zxZJ;0OQ=nioGnFDakhXlwLiv!Q7wy6S|Wj`SwP%CdoF;Tf+<kc9XG0J`FmoF%#3_7
z4?~=8XocE)nR;fIA)>~`@al{SZG+&y#UkuikQ?a!HPMMmgei*qFvOcBL*RH;QbX#B
zwYc6o6U~+7VTfcUnT<WR#DUx??)GIL-9cc$H;t(!kQ@6HiEQD<JsO3G#Ibu6TRq2>
zd8M0hSy%m=?P84s2t<HtRrKo+9h>)8+hv?y<JJoQPO``2j$W>>6=PN0cP&BKsdI+b
z37JJL1_D~Yj=635g;Cwql%i2PR_8CJx*r9eD8nYhJ>=)y+G|e59_a!oa7@ZBb>xS-
zZJdt;Gil3AW9vPxSm(#YeZ9b?`RLHM=p6<I29VeR-XhrjCvMSq;veJ(!GsF94FJw#
zy`KDrl7rAJIFuY@FkbcMf#n=;qu|leFZ|7*4x%~^4x)K#>I*3Z>_3G@XjL;Six@fd
zRj69a1l68`$!{pR0(qL-+g}t)gFE0CE-cV-&}s+Hvw-!cP_ZevUI>O)pzZIM!?6nr
z$Rb&JW@ZLNYE=g_bpIn{zwyWKkI@;tcgMv4w0l>KE=2$Bv;Zr@SE7@6d?SY7xF5Q_
zDK4Pqh~;m?cfFp`?T(pWnwc2H7%+=h<>_~DAK@ufhUCs)9}zIeWuZ;snWjxa@MiDE
zosx1IF0wV=_D;na^%OzmHY~MJJv=D7Am+3vCVdUYq}e}ZJ~PRAc{nlK`m#E*LsQ<T
zZxAb8Iu=_l+n^D#C6Yl^eo$$4vS8Zoeq&&ocTN7Cx$!D}|6!xrj`_Bc_3#VuOL22a
zb^45MpTJrIZ4}GM^@(233Sifeywkb2@za&^Piob<d)Q6Y@`BpuU!>6-oRS+WYlm#D
zC1<}31|ATb4d+|G3;_^p>QBvujfMgp;{wZvY!scfohYsf<+C(ttldPr{C(`h4MrG!
ziF)+M?ARJH?0e6?+st(=Np>iC!dQawr#G-n;I2d?6Mx?|tQ-SZBucydUXIRkhVY&>
zd8=c>t$@8`NiKOG8P_YoUb;_WWz)z3p$r90WCrwCN0HNFPy@8WfqJExkz~Y=<#y~R
z1&&DW_uFomYlcErW+>@G#yCyrnut5%;NO7HVk1q1M#5Qsb25c<SPZHMV>Z6zrY<`!
z9d?{?v{i_Tu@vN1BGW`Bk*tetfI}W{l4o@s>48}$h@o^d{G}R&qXyfPButkBWb^4I
zwDPA2r}Mng95bF6BXB+ZYj>V8rQZE<ASqA_>D063yB)I4MCTFbWWmgnbL%b%92tk*
zv%WtUDfl_mvi<WSlzv9BL>ujW+gqvsNe;2hl@~eNeE}EQ&F{*ji1G{{@<y`^Hr}h*
zk)+-lN3-}aJtNOz_Z$Edo4zVu@_w5M`d`@Fp_&?mydI!74W6nAdey2`t5>fEzXl<0
zt*VBAjyf<z2s-MZ!Xz-z9r5zOST`t(fD4AuGywQ12sU$o3>yk}L!$t@Umm#N0;UYX
zfdC$l2l8yy9W7PmdQtvFbx;dhaDhA>P_?P*xZ%Z%3qB52XEd~?1qs&Rh!)t-0WoY<
zkH#A-k-#oJDA~Ozi=wKbfcMi+KY^cupl$-1GC<t~(ySr={rW*~{#a`H4@B&mcVX3q
zXnI@yU@1OqLk`zp>hA}Gqq8Qs@g?Q3z~LJ9v-?)Zh?RZQbRb-}npoO}aRT&1tEtf*
zxv~kz8&-`CYmfCeWRI3J+Bc-wO*r$);w-)pJ?-eLYfo;?nD0JQ+bC|I*q9KOCf0E&
zV~*U9WotMUro~k-pH16g;o;vh#Y+g(<E1;B@06PkB%?~RTl7Vd^Nv-eyyguBFnaHo
zhJ4=>d(OuilGlb`s@I60f7<I3ZYCvjP`YGv$o9_kS@8fV@}%%U_~3+NK0mj4D6Q$T
z9K#Pj{@k2<^k+vaeq1wyJ2)_H#!qMt=l*TL$(*0md?%bfC^zS)MD9AmsEX{!BOP7O
zE)0-9o@82w<L-xI!zafD*zl@`k&qDj6kI1HXk26xlIDJu<i8G;9k70#WGOk8Tpntt
zSR&CRXGHe*@4VyNKDaEzY&X)$AreI=2hAmopu=-fz+PGloI#hi$FOY-uf>cWwo8kZ
z=;mkUN@?yCssg`d{bUc%=A3#q+p%x8!fY#LUg7Mxv4c-Jb9aVqP?tWZgV>785@NP(
z*wyP`an4W1cF@QJ4sGprx1#3XjxpY7s%XoOvdH%^qR29U2Vs;{PC-NBBej8oDtIjS
za!TUZ>2IjV?{bXQ-ST}6Nd+9?R0L7?w!+c%d-E?5`(1DXmoofH8X90WxTt$;+-X4E
z+P%NUASKVJ$h>#ob~mj!VgNGg;n@?tpYTSP#9g@XDcpav)85)V$sA^0-MlIg6<|e~
zEAl}c7*IDM2xW?;Es2A03cAQ8gNe`mHH~laVPBfRb#_eL?5n;d+S@#M#>U2rEYfe2
ztN+nc<=LR=L$&x6a!fB;NCf5WH!ci7NtQ+7Ldc{C&b>hMv|vaa+(v}#9w60@iHU(W
zxS-u|D9-}UaH%@<va(+II~MhHfIhuN^R%j%4$ad-ol8>b!qk9VE{8T<AT$T322?Q}
zB;t_E18iX$9DFr309B-pjV*Zdz|0F2Du-rW7FqIC9n(*qy!xno8%GBTx~la9%D;fO
zNL+dSAe4u|UDdhw2UPywhw`xJYW@EZUdy8g+oV^EJL)eD<AZdJx35~U&-c)w<-sMb
z_D4Crr4C0@(9-fq@=B(nHg@$x$MgQ2I(SIY)4|)?{b2;#a~#*9S|ufpbU9A=Xx2>9
zx~?&ru+${WT+mmqM6rZQtxnI#+=<bp-xslr6v|9O+sHSqF}R1^B@uyi&qD|O?k+u|
zbxL%-xGO=&W7BwRLGWtcc3iD}=Ndw3ds+Nrt!{(gx~fV?^zQn?dy8@7PLqzEi8c+_
zra987=1{9Ic;1N#Wvwf%Ht?1w;Ve7r(a+af?%y_v={$^9=M_cb@SU0HX{#b{sZ-}c
z^p9DatS4t%v!=8n(wrnuD@hq$xFJSeS3*9iu=+D3F4gk>4TsdR))7{`_M{*pDy?-S
zI4aX;m=sVf>0?}7-*KN5b9*;~p;wYHM*5^{l*%YPJo+`MvP+Vkf?a}L8eK_NYD5<v
zBfz3J*k~&D#cQTv=p9pCRpg7!Y7rc4KQUbIdZ6909g^Zc+Gg`$GDF9&LKEri(KXrQ
zf|`WgGB}+v$3X7-I#x(vDEs?9M7NH_g$L210Q<<+-3ipDUzxXb>NrYlw128df$bQT
zY^7e01WeSO`XT_A4{zdbQER227OELOqEFCdyIgGp{9A|2`)oVhIeNHcxUI8#z`e1X
zs4JKlb)%2Tyg~fX7wQFQ*2Nnx>OuHlgoF=MoKH40r~F2+tBX&#h1fNFJfbxRww~-H
ziH{($hrZZ!M0@AON)hJzqpPKQ$()}<R~Km1{9u^(SMM+FGg7inQ~|P9+CeW%LXm?A
zExKZ{^CDuvz9jKgPsd}f-`ha@-TxThcpx&X-a@Ly;qUy_po|R;zd&Uj5Hc@X<bwJ-
zKn)voP=m+}wRS+rtm>nF+i*CfYJ-Iykh6M`ts3gD-nVZbG+C<}`&MnnD|vaUhz%B&
z9y#(VkO-}CL9|;YlQlOtpE~uzg$F`3RAdc)T0((DXh$53C4xpgFw_F#v+53v>Y6QN
z$5WNML5O~V&QD&|<v~PTm5VR3=|L3yjc^U6UZ7KD>JTRXzX0;P+&dcdUXg5h^dL8;
z&29h2WaX%auXgat73a-))egc0FUPzd#TopuYUGN3{Kh$wUA;lVV9bya-%X}>j+vm-
zH!={kZzb~1MuK*WFh4Z4I=z*p(*~<WTR2|vyN)sWbJyfZpq-l&t2Z>zlh-Udwf1ZS
zks{QKGOVWB^8ykvCqLWtn|Ro-tp9F)`oOfinOtMc2U#T0Ob;~}vq$X8ysfiTHkjx#
zKs}J3-&`f;476q4&U1+umk!*|lySBnmaIS9wkJQZ@1~1$zaV#V1~KOCy!rV@nG-Yc
zF{<<aj}pFS!lTYQc?(g)Hw?yto#Ta*518vlS<VG*6{L>^37Z&41-N|DP62y}ArvI!
zlXeUG2N=ZyX+Eh?fGTCw9R3#W#9=ptqBb2qG)!_!(d=V5r${u&o;y6Xd7P>xLbO+d
zhE`Or4_QZRQ~!-4^eYo**)9mL@!0LB=yO_8MG~d19tVLDx;5;OQt0c}e!?Md+(r~(
z&+L$BQI1A_#$J9Tr+p}N)6qi#l(a6-Y`k@Uk6~0|q&P6H)?*Oia&V_-uan7oKf5Te
zJyO8Qrwi2+W-3{7BuEx3rl+U290gVaDTRkjjt7Z{(K|(KP5;I#=-6r9^kXOlPO`JG
zuL|AoH`U<OyyP3dLAZME=E{}hSN&^m$0P&JK8P!r8ko^?2LZf?YG}W%sLQZvUpnLC
zzm)ybjB+k)*G2!S;=7$dO!3}_%YQh(y%*ys4UNB4$z@({dju%l{=EJu{Ib38-nKl^
z{QXqEOJvlUK)d~^v(C|2f~{5q!ouz<CbLVCXThBqzhYAyJvuw|dpX`2#@mqF)YNoA
z$X2;Gg7mu_2;qwoiIA^aH7^dDNTC17MRCNnYhSf^yqFU3S#XemC=TrjKr4z&=9}Fl
zQBg1cBO!_nwc#Zty<pkkoMLL~0?`IpHdMs~ZQO#&_M(1MaNO&SX>uqE14-Pf%U-IV
zln~>FupHvr;D_b;^Dm_Dix(Hx2~;;?AWI5ZL=A>xz#?jB9R_p<j*gD1PJgNLaaHXF
zbgMuL%o|%jRKGSCVZ3TG=8a7Jb-E2|Desbx`8O%&u;*%5-<fXH1wA81dv}juSNpAk
z+u)TVEZ%lyf_hWaZ15MytM+UZpPl?R#M#gB=Z$f5-!6-yV|%6*O8haILFSmO;u#gw
zY#>dg2r$7bPwUSXUlQuHkeIfeZi>mXHE!n7pV3Zy<*paulwpAk13dYsyL3lad&cU_
z_9U;7ADC<^DuJgU4cVSOt47NWSFWIEgi0O`2E-lY7L=!uzch6TiYrpED68@%@+~{|
zHpnsgNpg|gOzw1#?^E$PTwd2y#J?a<M3^@?dGIUcI!O8LDLnsA^5^@I6&D`;ke4Hu
zBb`n-RkgDp>FnAuI$kL+&pn@%d?og%oIk4!WFI6dsqDkE$}M4eMDdm|5p15$77{1u
z?ENqtjeV5Z7r@>I%cZa{tRq(f6xh69n6FPCE38>b(8-7Q1Ec1it$-xS;98q+7-1p;
z>ES}~?AZdR)O<}(XKCh=3t97ihp?V!;==w!V_lO_n*h<)pm`smB|G=t5bbE{=sofX
z;R?pxb{s=ud$YoTy+K}C_5Q)BngbL@ct@`PwWMwoqLnRXM6>DRSE(29-KgbfI7R~-
zdt>`_4B{y=L^)f?jG+t;XZ1wJr{?IP;%(J5tZP{t!~@~+zolCE526)L>i7%=tw1OC
z0o~l|@ifWR`qBM|CoN!URBMTBWZ31nd+ildk0*s)hON7EmK)v4JK<`!yQE!9`+mcB
zENcg;=-B!D=1Uks*VX)-FGeeTXsl~9Stg%<&~lpknIA4&t8u2`P%gIMW<SFUsQ$`H
zn`Of-i`T%F7GQH{04?+$Q?9mShq!3=_wkp<PbvR5ppC=*9RY{(Fp$K(pl-Xqpx)5E
zE|Y*5u1do}4!+1r0<H%@8VT4%&15p!Y&Hnjn>R01eSnHcV&V(9hWbmP4KHx+HY4Lj
z!-pzFt1fw|`bbpQyB3AULBs~TmQ=?9py^w%ni}c?0R@t_wl;9X>-8Z<Fh;(}t_`*?
zy)9J^?E^e`d5|6sR-3*~y;bY?RH--QCWP=Ax&<Eoa;y>L+KZHuKY-{(_eL`QE@b(C
zfA2VhODfdemV!89eVxkV;PM$d8txuVk7-wjVdX@GZbg&O?6{fN$D;89g-}rKTM6$d
zU*<Ss8sK1l=Du=UlMKEC*JRFunF5iPBJ&<pV(Ge=-nDb6`p0luGB$X8Eh%KE)8L0x
z6c_aaiEblzYMuP4#>0|*%H8=_cnI5}*jlG_T)C`yK!1{znR71oq)G6f(|2}W-lh{(
zXEu^LWIyj06ufnU+j7HW#H$^(A#I=6PrivOvXR}DDWO$(f2#xBX<T!|>OjUljkIgy
zc8&a=JNk^FSWII@&vOiS=$x(1iP#lhsEN}hVOwLfy>>mgQ5@EB>zF+4_T_DDryGg6
zeA2hJD68<w=5GbqU>vf{F4*zD03SSwD8mE;_iuRG+DIso6}Iz(q;)o)q+Nh*mU4NK
z7a0{giAX36WBYLO!(z!2ewg=qqv0Um+0#a=KCsbLcSZP)i*3n|##u-t%2cf1H_Ta{
zK-0t^T(fLPD;Qgd(%#L8gmEz}wP3C)>=092s2x1$j-h?pZu9V<Q8zGZkh8ZhoP8jY
zJgm?5^lhM?lua2K><K5sA_A)$6b>6$I3CU{wp*fK9!JnF3!YUN8b%lR@I6q{r@64`
zsBXYbN)bz!+PH2I7g<!NvB?s26^|4JoAL4gNN4?*G-G!)?;J;K30LDZOrVx)#v_l=
zKf0CfH(;{br^pAHd4LmG{+F_=Y$I6-hnD*AY*CjnUfxG<{(j%`oa})0&;0e~xu|T*
z!_>*E7LR`{QiE+e9ct=&h@g<3bEM7sJJq#dV8#+Ir3Vp>7oGg5-o=k*o!-^Ww=J2Q
z{OWhQt^4s?+#_%}T!m%-^ECU72XXHO%l7Z1UJ(6W6l;VQ7@==UsQUv%Y|u#p$|um6
z0AvKD)9K&{Bgnm>FH7jZ60D(yYVwkjlE4%V*gFlPH<(%kOHaXqJl@O8&VI374#gM2
zjW6&-qQCp51apk4(EaJB3zA9Q%L7A<kj@QdZ$O_3sGxvW6DY(8wmK~`2tw&|s6_AP
z%@<Q(syg>K-z<!PLFf(5yr|OY*QZ^eWiW_fLm!$@1|8B)7MTUXQ`N8`$ppMNV)eg?
zQUAfgo~xaE2eR$8Vf%c6sCL6nV(zcR?*tOmvbZ5uJ2$>%LnwLRu30G%RNOVyb&sx8
zt3Qq^T2cYD<8k$dfaSJ3Muy6KF^9S1{3ChqHm*;~j-LmeNt;-gFXyC9med9`ijQv2
zFB-UQo?wrv{DnDI+Oq1g#sdprIophzfhdvj<wAB*>$J;SO4<a!yWDv6>Eo4sUO~qP
z<{!gv71jEt3J)YTOcenqEZaf>BW4OewQ$-rbAOGfmau<C274|)H7~c&c<tvm7>bBt
z)Xz4_>-O~^$cjjR+A)0X6RkDRQB@j$iK(mobjU=5)h^k`J6G_1)rr8vzx|Tz<i!aw
zdK%5{@M@<4PS<c&zy{BBIv|{xrvc%f1O~vLk)VNkA|Vjq%uuMnGf#pBux>`W6d?H+
zQGtZLliGZEl|RceaS#w=;hLrD1s8v-A1+uYbq^$mYQQAcVmikkAn5)WmPi@51zg;W
zJheh+XD9qz44VL@j%MDRb|CqZFR)@GB6sN#n`>MWu!`sf#8V1>RJaEc5;jqWW^Ra$
z*C*EN8VBhiWaRR4ctx`L_|pi$o+DD^%DVG}cwZ*FhHAZf(htpL80n&2ne03oB~(I6
zMcBsmhKrf$mxZpocX*O5%G~S<EspM_Xw+F=dpZ@#m`W>fXuS7S*R3%llx{_rC3G2{
z$*C`n9Z3v8RevyPTXy!IxjWA)g2LU{Ir908IO2q$6?;CY$!D2!PL2BI3p1C+ex^-(
zeq`9P?p5y4VZnP6Q#x&)jzqdeX2Di`wf-4{?$E8^W5gLFLxdl8{l5KTs;Qyot@i%j
V-}`%i@9+J+zxVh4-rv9A{u}xRWtji~

literal 0
HcmV?d00001

diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js
index 01c21044d2..a066143d45 100644
--- a/scripts/system/marketplaces/marketplaces.js
+++ b/scripts/system/marketplaces/marketplaces.js
@@ -800,6 +800,18 @@ var selectionDisplay = null; // for gridTool.js to ignore
         }, 150);
     }
 
+    function signalNewResourceObjectInTest(resourceObject) {
+        sendToQml({
+            method: "newResourceObjectInTest",
+            resourceObject: resourceObject });
+    }
+
+    var resourceObjectsInTest = [];
+    function storeResourceObjectInTest(resourceObject) {
+        resourceObjectsInTest.push(resourceObject);
+        signalNewResourceObjectInTest(resourceObject);
+    }
+
     // Function Name: fromQml()
     //
     // Description:
@@ -856,9 +868,22 @@ var selectionDisplay = null; // for gridTool.js to ignore
             case 'checkout_rezClicked':
             case 'purchases_rezClicked':
             case 'tester_rezClicked':
-                print("marketplace.js going to rez");
                 rezEntity(message.itemHref, message.itemType);
                 break;
+            case 'tester_newResourceObject':
+                storeResourceObjectInTest(message.resourceObject);
+                break;
+            case 'tester_updateResourceObjectAssetType':
+                var objectId = message.objectId;
+                for (var i = 0, size = resourceObjectsInTest.length; i < size; ++i) {
+                    if (i in resourceObjectsInTest &&
+                        objectId === resourceObjectsInTest[i]["id"]
+                    ) {
+                        resourceObjectsInTest[i]["assetType"] = message.assetType;
+                        break;
+                    }
+                }
+                break;
             case 'header_marketplaceImageClicked':
             case 'purchases_backClicked':
                 tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL);
@@ -1027,6 +1052,22 @@ var selectionDisplay = null; // for gridTool.js to ignore
         }
     }
 
+    function pushResourceObjectsInTest() {
+        var isQmlSignaled = false;
+        for (var i = 0, size = resourceObjectsInTest.length; i < size; ++i) {
+            if (i in resourceObjectsInTest) {
+                signalNewResourceObjectInTest(resourceObjectsInTest[i]);
+                isQmlSignaled = true;
+            }
+        }
+        // Be sure that the QML has heard from us, at least so that it
+        // can indicate to the user that all of the resoruce objects in
+        // test have been transmitted to it.
+        if (!isQmlSignaled) {
+            sendToQml({ method: "marketplaceTestBackendIsAlive" });
+        }
+    }
+
     // Function Name: onTabletScreenChanged()
     //
     // Description:
@@ -1070,6 +1111,9 @@ var selectionDisplay = null; // for gridTool.js to ignore
             onMarketplaceItemTesterScreen);
 
         if (url === MARKETPLACE_PURCHASES_QML_PATH) {
+            // FIXME: There is a race condition here. The event bridge
+            // may not be up yet. Suggest Script.setTimeout(..., 750) to
+            // help avoid the condition.
             sendToQml({
                 method: 'updatePurchases',
                 referrerURL: referrerURL,
@@ -1103,6 +1147,12 @@ var selectionDisplay = null; // for gridTool.js to ignore
                 method: 'inspectionCertificate_resetCert'
             });
         }
+
+        if (onMarketplaceItemTesterScreen) {
+            // Why? The QML event bridge, wired above, needs time to
+            // come up.
+            Script.setTimeout(pushResourceObjectsInTest, 750);
+        }
     }
 
     //

From 7f189e4d10f87f7f6ae4bdbc259683928356a41b Mon Sep 17 00:00:00 2001
From: sabrina-shanman <sabrina@highfidelity.io>
Date: Mon, 24 Sep 2018 17:15:56 -0700
Subject: [PATCH 42/56] Make visualPickResult used by Pointers a private copy

---
 libraries/pointers/src/Pointer.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp
index 031baece5f..bdf1250a8d 100644
--- a/libraries/pointers/src/Pointer.cpp
+++ b/libraries/pointers/src/Pointer.cpp
@@ -68,7 +68,7 @@ void Pointer::update(unsigned int pointerID) {
     // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts
     withReadLock([&] {
         auto pickResult = getPrevPickResult();
-        auto visualPickResult = getVisualPickResult(pickResult);
+        auto visualPickResult = getVisualPickResult(std::make_shared<PickResult>(*pickResult.get()));
         updateVisuals(visualPickResult);
         generatePointerEvents(pointerID, visualPickResult);
     });

From 5dbebd4aae4652fa0109d3da9e11f93cf85151a1 Mon Sep 17 00:00:00 2001
From: sabrina-shanman <sabrina@highfidelity.io>
Date: Mon, 24 Sep 2018 17:36:24 -0700
Subject: [PATCH 43/56] Add comment explaining why visualPickResult is a copy

---
 libraries/pointers/src/Pointer.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp
index bdf1250a8d..852a83c192 100644
--- a/libraries/pointers/src/Pointer.cpp
+++ b/libraries/pointers/src/Pointer.cpp
@@ -68,6 +68,7 @@ void Pointer::update(unsigned int pointerID) {
     // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts
     withReadLock([&] {
         auto pickResult = getPrevPickResult();
+        // Pointer needs its own PickResult object so it doesn't modify the cached pick result
         auto visualPickResult = getVisualPickResult(std::make_shared<PickResult>(*pickResult.get()));
         updateVisuals(visualPickResult);
         generatePointerEvents(pointerID, visualPickResult);

From 0a43c17ce0d97458e57b972a92107806d5837be6 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Mon, 24 Sep 2018 21:15:08 -0700
Subject: [PATCH 44/56] Use entity-specific glyphs in marketplace item tester

---
 .../MarketplaceItemTester.qml                 | 21 +++++++++++++------
 1 file changed, 15 insertions(+), 6 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index f2dbcc3f80..40e4f55f2c 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -188,13 +188,22 @@ Rectangle {
             }
 
             Repeater {
-                model: [
-                    { "name": "forward", "glyph": hifi.glyphs.forward, "size": 30 },
-                    { "name": "trash", "glyph": hifi.glyphs.trash, "size": 22}
-                ]
+                model: [ "forward", "trash" ]
+
                 HifiStylesUit.HiFiGlyphs {
-                    text: modelData.glyph
-                    size: modelData.size
+                    property var glyphs: {
+                        "application": hifi.glyphs.install,
+                        "avatar": hifi.glyphs.avatar,
+                        "content set": hifi.glyphs.globe,
+                        "entity": hifi.glyphs.wand,
+                        "trash": hifi.glyphs.trash,
+                        "unknown": hifi.glyphs.circleSlash,
+                        "wearable": hifi.glyphs.hat,
+                    }
+                    text: (("trash" == modelData) ?
+                        glyphs.trash :
+                        glyphs[comboBox.model[comboBox.currentIndex]])
+                    size: ("trash" == modelData) ? 22 : 30
                     color: hifi.colors.black
                     horizontalAlignment: Text.AlignHCenter
                     MouseArea {

From ca1da9c830d8c3bab0a5ea65dda5cc4c3d1c3f32 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Mon, 24 Sep 2018 23:07:44 -0700
Subject: [PATCH 45/56] Implement delete-from-list functionality in marketplace
 item tester

---
 .../MarketplaceItemTester.qml                 |  8 +++++--
 scripts/system/marketplaces/marketplaces.js   | 24 +++++++------------
 2 files changed, 14 insertions(+), 18 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index 0a852780b4..c962c84c31 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -136,6 +136,9 @@ Rectangle {
                     if ("application" == assetType) {
                         Commerce.uninstallApp(resource);
                     }
+                    sendToScript({
+                        method: "tester_deleteResourceObject",
+                        objectId: resourceListModel.get(index).id});
                     resourceListModel.remove(index);
                 }
             }
@@ -174,13 +177,14 @@ Rectangle {
                     "unknown"
                 ]
 
-                currentIndex: ("entity or wearable" == assetType) ? model.indexOf("unknown") : model.indexOf(assetType)
+                currentIndex: (("entity or wearable" == assetType) ?
+                    model.indexOf("unknown") : model.indexOf(assetType))
 
                 Component.onCompleted: {
                     onCurrentIndexChanged.connect(function() {
                         assetType = model[currentIndex];
                         sendToScript({
-                            method: 'tester_updateResourceObjectAssetType',
+                            method: "tester_updateResourceObjectAssetType",
                             objectId: resourceListModel.get(index)["id"],
                             assetType: assetType });
                     });
diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js
index c279e76b16..aba4ba5f7d 100644
--- a/scripts/system/marketplaces/marketplaces.js
+++ b/scripts/system/marketplaces/marketplaces.js
@@ -770,18 +770,13 @@ function maybeEnableHMDPreview() {
     }, UI_FADE_TIMEOUT_MS);
 }
 
+var resourceObjectsInTest = [];
 function signalNewResourceObjectInTest(resourceObject) {
     ui.tablet.sendToQml({
         method: "newResourceObjectInTest",
         resourceObject: resourceObject });
 }
 
-var resourceObjectsInTest = [];
-function storeResourceObjectInTest(resourceObject) {
-    resourceObjectsInTest.push(resourceObject);
-    signalNewResourceObjectInTest(resourceObject);
-}
-
 var onQmlMessageReceived = function onQmlMessageReceived(message) {
     if (message.messageSrc === "HTML") {
         return;
@@ -835,18 +830,15 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) {
         rezEntity(message.itemHref, message.itemType);
         break;
     case 'tester_newResourceObject':
-        storeResourceObjectInTest(message.resourceObject);
+        var resourceObject = message.resourceObject;
+        resourceObjectsInTest[resourceObject.id] = resourceObject;
+        signalNewResourceObjectInTest(resourceObject);
         break;
     case 'tester_updateResourceObjectAssetType':
-        var objectId = message.objectId;
-        for (var i = 0, size = resourceObjectsInTest.length; i < size; ++i) {
-            if (i in resourceObjectsInTest &&
-                objectId === resourceObjectsInTest[i]["id"]
-            ) {
-                resourceObjectsInTest[i]["assetType"] = message.assetType;
-                break;
-            }
-        }
+        resourceObjectsInTest[message.objectId].assetType = message.assetType;
+        break;
+    case 'tester_deleteResourceObject':
+        delete resourceObjectsInTest[message.objectId];
         break;
     case 'header_marketplaceImageClicked':
     case 'purchases_backClicked':

From ff83713365b4b4fa5c69f733506d656f2745a826 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Mon, 24 Sep 2018 23:57:48 -0700
Subject: [PATCH 46/56] Use persistent object id counter

---
 .../MarketplaceItemTester.qml                 | 17 +++++++--------
 scripts/system/marketplaces/marketplaces.js   | 21 ++++++++-----------
 2 files changed, 17 insertions(+), 21 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index c962c84c31..a0ccf91baa 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -26,13 +26,11 @@ Rectangle {
     id: root
 
     property string installedApps
+    property var nextResourceObjectId: 0
     signal sendToScript(var message)
 
     HifiStylesUit.HifiConstants { id: hifi }
-    ListModel {
-        id: resourceListModel
-        property var nextId: 0
-    }
+    ListModel { id: resourceListModel }
 
     color: hifi.colors.white
 
@@ -52,7 +50,8 @@ Rectangle {
                 resourceListModel.append(resourceObject);
                 spinner.visible = false;
                 break;
-            case "marketplaceTestBackendIsAlive":
+            case "nextObjectIdInTest":
+                nextResourceObjectId = message.id;
                 spinner.visible = false;
                 break;
         }
@@ -65,14 +64,14 @@ Rectangle {
                          resource.match(/\.json\.gz$/) ? "content set" :
                          resource.match(/\.json$/) ? "entity or wearable" :
                          "unknown");
-        return { "id": resourceListModel.nextId++,
+        return { "id": nextResourceObjectId++,
                  "resource": resource,
                  "assetType": assetType };
     }
 
     function installResourceObj(resourceObj) {
-        if ("application" == resourceObj["assetType"]) {
-            Commerce.installApp(resourceObj["resource"]);
+        if ("application" == resourceObj.assetType) {
+            Commerce.installApp(resourceObj.resource);
         }
     }
 
@@ -269,7 +268,7 @@ Rectangle {
                     sendToScript({
                         method: 'tester_newResourceObject',
                         resourceObject: resourceObj });
-                    }
+                }
             }
 
             Repeater {
diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js
index aba4ba5f7d..2e0c05be43 100644
--- a/scripts/system/marketplaces/marketplaces.js
+++ b/scripts/system/marketplaces/marketplaces.js
@@ -980,19 +980,16 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) {
 };
 
 function pushResourceObjectsInTest() {
-    var isQmlSignaled = false;
-    for (var i = 0, size = resourceObjectsInTest.length; i < size; ++i) {
-        if (i in resourceObjectsInTest) {
-            signalNewResourceObjectInTest(resourceObjectsInTest[i]);
-            isQmlSignaled = true;
-        }
-    }
-    // Be sure that the QML has heard from us, at least so that it
-    // can indicate to the user that all of the resoruce objects in
-    // test have been transmitted to it.
-    if (!isQmlSignaled) {
-        ui.tablet.sendToQml({ method: "marketplaceTestBackendIsAlive" });
+    var maxObjectId = -1;
+    for (var objectId in resourceObjectsInTest) {
+        signalNewResourceObjectInTest(resourceObjectsInTest[objectId]);
+        maxObjectId = (maxObjectId < objectId) ? parseInt(objectId) : maxObjectId;
     }
+    // N.B. Thinking about removing the following sendToQml? Be sure
+    // that the marketplace item tester QML has heard from us, at least
+    // so that it can indicate to the user that all of the resoruce
+    // objects in test have been transmitted to it.
+    ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: maxObjectId + 1 });
 }
 
 // Function Name: onTabletScreenChanged()

From eca31e7a994a5a0c7841e82eda987f99f972ba5c Mon Sep 17 00:00:00 2001
From: sabrina-shanman <sabrina@highfidelity.io>
Date: Tue, 25 Sep 2018 09:20:57 -0700
Subject: [PATCH 47/56] Fix instantiating abstract class when creating visual
 pick result for pointers

---
 interface/src/raypick/LaserPointer.cpp    | 5 +++++
 interface/src/raypick/LaserPointer.h      | 2 ++
 interface/src/raypick/ParabolaPointer.cpp | 5 +++++
 interface/src/raypick/ParabolaPointer.h   | 2 ++
 interface/src/raypick/StylusPointer.cpp   | 5 +++++
 interface/src/raypick/StylusPointer.h     | 1 +
 libraries/pointers/src/Pointer.cpp        | 2 +-
 libraries/pointers/src/Pointer.h          | 1 +
 8 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp
index 5fbe3a90b5..64faf5f9bf 100644
--- a/interface/src/raypick/LaserPointer.cpp
+++ b/interface/src/raypick/LaserPointer.cpp
@@ -35,6 +35,11 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant&
     }
 }
 
+PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
+    auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
+    return std::make_shared<RayPickResult>(*rayPickResult.get());
+}
+
 QVariantMap LaserPointer::toVariantMap() const {
     QVariantMap qVariantMap;
 
diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h
index c0ac3259d9..b391f60f85 100644
--- a/interface/src/raypick/LaserPointer.h
+++ b/interface/src/raypick/LaserPointer.h
@@ -47,6 +47,8 @@ public:
     static std::shared_ptr<StartEndRenderState> buildRenderState(const QVariantMap& propMap);
 
 protected:
+    PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override;
+
     void editRenderStatePath(const std::string& state, const QVariant& pathProps) override;
 
     glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override;
diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp
index 888b3ddbe8..e7f54d2e97 100644
--- a/interface/src/raypick/ParabolaPointer.cpp
+++ b/interface/src/raypick/ParabolaPointer.cpp
@@ -30,6 +30,11 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap&
 {
 }
 
+PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
+    auto stylusPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
+    return std::make_shared<ParabolaPickResult>(*stylusPickResult.get());
+}
+
 void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) {
     auto renderState = std::static_pointer_cast<RenderState>(_renderStates[state]);
     if (renderState) {
diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h
index 526abe3b0d..8fb864c07b 100644
--- a/interface/src/raypick/ParabolaPointer.h
+++ b/interface/src/raypick/ParabolaPointer.h
@@ -102,6 +102,8 @@ public:
     static std::shared_ptr<StartEndRenderState> buildRenderState(const QVariantMap& propMap);
 
 protected:
+    virtual PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override;
+
     void editRenderStatePath(const std::string& state, const QVariant& pathProps) override;
 
     glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override;
diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp
index 06e3e52d21..7f05a706a4 100644
--- a/interface/src/raypick/StylusPointer.cpp
+++ b/interface/src/raypick/StylusPointer.cpp
@@ -147,6 +147,11 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) {
     return false;
 }
 
+PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
+    auto stylusPickResult = std::static_pointer_cast<StylusPickResult>(pickResult);
+    return std::make_shared<StylusPickResult>(*stylusPickResult.get());
+}
+
 Pointer::PickedObject StylusPointer::getHoveredObject(const PickResultPointer& pickResult) {
     auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);
     if (!stylusPickResult) {
diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h
index 4095acb529..ff60fd78e5 100644
--- a/interface/src/raypick/StylusPointer.h
+++ b/interface/src/raypick/StylusPointer.h
@@ -42,6 +42,7 @@ protected:
     Buttons getPressedButtons(const PickResultPointer& pickResult) override;
     bool shouldHover(const PickResultPointer& pickResult) override;
     bool shouldTrigger(const PickResultPointer& pickResult) override;
+    virtual PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override;
 
     PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
 
diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp
index 852a83c192..26460cbdd7 100644
--- a/libraries/pointers/src/Pointer.cpp
+++ b/libraries/pointers/src/Pointer.cpp
@@ -69,7 +69,7 @@ void Pointer::update(unsigned int pointerID) {
     withReadLock([&] {
         auto pickResult = getPrevPickResult();
         // Pointer needs its own PickResult object so it doesn't modify the cached pick result
-        auto visualPickResult = getVisualPickResult(std::make_shared<PickResult>(*pickResult.get()));
+        auto visualPickResult = getVisualPickResult(getPickResultCopy(pickResult));
         updateVisuals(visualPickResult);
         generatePointerEvents(pointerID, visualPickResult);
     });
diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h
index 4264a60079..173163374f 100644
--- a/libraries/pointers/src/Pointer.h
+++ b/libraries/pointers/src/Pointer.h
@@ -91,6 +91,7 @@ protected:
 
     virtual bool shouldHover(const PickResultPointer& pickResult) { return true; }
     virtual bool shouldTrigger(const PickResultPointer& pickResult) { return true; }
+    virtual PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const = 0;
     virtual PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) { return pickResult; };
 
     static const float POINTER_MOVE_DELAY;

From 660bdf36d01b83996aacc93e9997eac41b70f463 Mon Sep 17 00:00:00 2001
From: sabrina-shanman <sabrina@highfidelity.io>
Date: Tue, 25 Sep 2018 09:56:11 -0700
Subject: [PATCH 48/56] Check if pick result is null when building visual pick
 result

---
 interface/src/raypick/LaserPointer.cpp    | 3 +++
 interface/src/raypick/ParabolaPointer.cpp | 3 +++
 interface/src/raypick/StylusPointer.cpp   | 3 +++
 3 files changed, 9 insertions(+)

diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp
index 64faf5f9bf..79bca0449f 100644
--- a/interface/src/raypick/LaserPointer.cpp
+++ b/interface/src/raypick/LaserPointer.cpp
@@ -36,6 +36,9 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant&
 }
 
 PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
+    if (!pickResult) {
+        std::make_shared<RayPickResult>();
+    }
     auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
     return std::make_shared<RayPickResult>(*rayPickResult.get());
 }
diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp
index 4614b81cbb..ec4222d5f6 100644
--- a/interface/src/raypick/ParabolaPointer.cpp
+++ b/interface/src/raypick/ParabolaPointer.cpp
@@ -31,6 +31,9 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap&
 }
 
 PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
+    if (!pickResult) {
+        std::make_shared<ParabolaPickResult>();
+    }
     auto stylusPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
     return std::make_shared<ParabolaPickResult>(*stylusPickResult.get());
 }
diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp
index 7f05a706a4..2742c68d1d 100644
--- a/interface/src/raypick/StylusPointer.cpp
+++ b/interface/src/raypick/StylusPointer.cpp
@@ -148,6 +148,9 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) {
 }
 
 PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
+    if (!pickResult) {
+        std::make_shared<StylusPickResult>();
+    }
     auto stylusPickResult = std::static_pointer_cast<StylusPickResult>(pickResult);
     return std::make_shared<StylusPickResult>(*stylusPickResult.get());
 }

From 6e83e96d1cff5c000fe30f22e052ec4815749c60 Mon Sep 17 00:00:00 2001
From: sabrina-shanman <sabrina@highfidelity.io>
Date: Tue, 25 Sep 2018 12:05:47 -0700
Subject: [PATCH 49/56] Check type in Pointer::getPickResultCopy

---
 interface/src/raypick/LaserPointer.cpp    | 4 ++--
 interface/src/raypick/ParabolaPointer.cpp | 6 +++---
 interface/src/raypick/StylusPointer.cpp   | 4 ++--
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp
index 79bca0449f..577978cc88 100644
--- a/interface/src/raypick/LaserPointer.cpp
+++ b/interface/src/raypick/LaserPointer.cpp
@@ -36,10 +36,10 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant&
 }
 
 PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
-    if (!pickResult) {
+    auto rayPickResult = std::dynamic_pointer_cast<RayPickResult>(pickResult);
+    if (!rayPickResult) {
         std::make_shared<RayPickResult>();
     }
-    auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
     return std::make_shared<RayPickResult>(*rayPickResult.get());
 }
 
diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp
index ec4222d5f6..92b82fff7f 100644
--- a/interface/src/raypick/ParabolaPointer.cpp
+++ b/interface/src/raypick/ParabolaPointer.cpp
@@ -31,11 +31,11 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap&
 }
 
 PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
-    if (!pickResult) {
+    auto parabolaPickResult = std::dynamic_pointer_cast<ParabolaPickResult>(pickResult);
+    if (!parabolaPickResult) {
         std::make_shared<ParabolaPickResult>();
     }
-    auto stylusPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
-    return std::make_shared<ParabolaPickResult>(*stylusPickResult.get());
+    return std::make_shared<ParabolaPickResult>(*parabolaPickResult.get());
 }
 
 void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) {
diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp
index 2742c68d1d..0b44b2705d 100644
--- a/interface/src/raypick/StylusPointer.cpp
+++ b/interface/src/raypick/StylusPointer.cpp
@@ -148,10 +148,10 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) {
 }
 
 PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
-    if (!pickResult) {
+    auto stylusPickResult = std::dynamic_pointer_cast<StylusPickResult>(pickResult);
+    if (!stylusPickResult) {
         std::make_shared<StylusPickResult>();
     }
-    auto stylusPickResult = std::static_pointer_cast<StylusPickResult>(pickResult);
     return std::make_shared<StylusPickResult>(*stylusPickResult.get());
 }
 

From 55ab0a394b81706e7d5776cf253258e688d014a8 Mon Sep 17 00:00:00 2001
From: sabrina-shanman <sabrina@highfidelity.io>
Date: Tue, 25 Sep 2018 12:21:00 -0700
Subject: [PATCH 50/56] Return std::make_shared

---
 interface/src/raypick/LaserPointer.cpp    | 2 +-
 interface/src/raypick/ParabolaPointer.cpp | 2 +-
 interface/src/raypick/StylusPointer.cpp   | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp
index 577978cc88..3c66923b4e 100644
--- a/interface/src/raypick/LaserPointer.cpp
+++ b/interface/src/raypick/LaserPointer.cpp
@@ -38,7 +38,7 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant&
 PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
     auto rayPickResult = std::dynamic_pointer_cast<RayPickResult>(pickResult);
     if (!rayPickResult) {
-        std::make_shared<RayPickResult>();
+        return std::make_shared<RayPickResult>();
     }
     return std::make_shared<RayPickResult>(*rayPickResult.get());
 }
diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp
index 92b82fff7f..33fa8738d9 100644
--- a/interface/src/raypick/ParabolaPointer.cpp
+++ b/interface/src/raypick/ParabolaPointer.cpp
@@ -33,7 +33,7 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap&
 PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
     auto parabolaPickResult = std::dynamic_pointer_cast<ParabolaPickResult>(pickResult);
     if (!parabolaPickResult) {
-        std::make_shared<ParabolaPickResult>();
+        return std::make_shared<ParabolaPickResult>();
     }
     return std::make_shared<ParabolaPickResult>(*parabolaPickResult.get());
 }
diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp
index 0b44b2705d..b648e125bf 100644
--- a/interface/src/raypick/StylusPointer.cpp
+++ b/interface/src/raypick/StylusPointer.cpp
@@ -150,7 +150,7 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) {
 PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const {
     auto stylusPickResult = std::dynamic_pointer_cast<StylusPickResult>(pickResult);
     if (!stylusPickResult) {
-        std::make_shared<StylusPickResult>();
+        return std::make_shared<StylusPickResult>();
     }
     return std::make_shared<StylusPickResult>(*stylusPickResult.get());
 }

From 5adc4c2290905a37b9668447ee0cbb2cb092022f Mon Sep 17 00:00:00 2001
From: Brad Davis <bdavis@saintandreas.org>
Date: Tue, 25 Sep 2018 13:17:49 -0700
Subject: [PATCH 51/56] Protect against null pointer usage in sound scripting
 wrapper

---
 libraries/audio/src/Sound.cpp | 7 +++++--
 libraries/audio/src/Sound.h   | 8 ++++----
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp
index 67f9952771..da284f19a3 100644
--- a/libraries/audio/src/Sound.cpp
+++ b/libraries/audio/src/Sound.cpp
@@ -43,8 +43,11 @@ void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPo
     }
 }
 
-SoundScriptingInterface::SoundScriptingInterface(SharedSoundPointer sound) : _sound(sound) {
-    QObject::connect(sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready);
+SoundScriptingInterface::SoundScriptingInterface(const SharedSoundPointer& sound) : _sound(sound) {
+    // During shutdown we can sometimes get an empty sound pointer back
+    if (_sound) {
+        QObject::connect(_sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready);
+    }
 }
 
 Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) :
diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h
index 348600e4ae..a0544870d0 100644
--- a/libraries/audio/src/Sound.h
+++ b/libraries/audio/src/Sound.h
@@ -105,11 +105,11 @@ class SoundScriptingInterface : public QObject {
     Q_PROPERTY(float duration READ getDuration)
 
 public:
-    SoundScriptingInterface(SharedSoundPointer sound);
-    SharedSoundPointer getSound() { return _sound; }
+    SoundScriptingInterface(const SharedSoundPointer& sound);
+    const SharedSoundPointer& getSound() { return _sound; }
 
-    bool isReady() const { return _sound->isReady(); }
-    float getDuration() { return _sound->getDuration(); }
+    bool isReady() const { return _sound ? _sound->isReady() : false; }
+    float getDuration() { return _sound ? _sound->getDuration() : 0.0f; }
 
 /**jsdoc
  * Triggered when the sound has been downloaded and is ready to be played.

From 15de0590e8ceaa44419781b4177b493d7851bfef Mon Sep 17 00:00:00 2001
From: Brad Davis <bdavis@saintandreas.org>
Date: Tue, 25 Sep 2018 13:18:49 -0700
Subject: [PATCH 52/56] Cleaning up commented out code

---
 libraries/audio-client/src/AudioClient.cpp | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp
index 6af74bc8e8..68a68633e6 100644
--- a/libraries/audio-client/src/AudioClient.cpp
+++ b/libraries/audio-client/src/AudioClient.cpp
@@ -285,9 +285,6 @@ void AudioClient::customDeleter() {
     _shouldRestartInputSetup = false;
 #endif
     stop();
-    //_checkDevicesTimer->stop();
-    //_checkPeakValuesTimer->stop();
-
     deleteLater();
 }
 

From 4e7b03883f2731160c036ae59fbb0c98daa22612 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Tue, 25 Sep 2018 15:13:26 -0700
Subject: [PATCH 53/56] Use triple equals

---
 .../marketplaceItemTester/MarketplaceItemTester.qml    | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index a0ccf91baa..8dfb277197 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -132,7 +132,7 @@ Rectangle {
                     }
                 },
                 "trash": function(){
-                    if ("application" == assetType) {
+                    if ("application" === assetType) {
                         Commerce.uninstallApp(resource);
                     }
                     sendToScript({
@@ -176,7 +176,7 @@ Rectangle {
                     "unknown"
                 ]
 
-                currentIndex: (("entity or wearable" == assetType) ?
+                currentIndex: (("entity or wearable" === assetType) ?
                     model.indexOf("unknown") : model.indexOf(assetType))
 
                 Component.onCompleted: {
@@ -203,10 +203,10 @@ Rectangle {
                         "unknown": hifi.glyphs.circleSlash,
                         "wearable": hifi.glyphs.hat,
                     }
-                    text: (("trash" == modelData) ?
+                    text: (("trash" === modelData) ?
                         glyphs.trash :
                         glyphs[comboBox.model[comboBox.currentIndex]])
-                    size: ("trash" == modelData) ? 22 : 30
+                    size: ("trash" === modelData) ? 22 : 30
                     color: hifi.colors.black
                     horizontalAlignment: Text.AlignHCenter
                     MouseArea {
@@ -257,7 +257,7 @@ Rectangle {
                 // Alas, there is nothing we can do about that so charge
                 // ahead as though we are sure the present signal is one
                 // we expect.
-                if ("load file" == currentAction) {
+                if ("load file" === currentAction) {
                     Window.browseChanged.disconnect(onResourceSelected);
                 } else if ("load url" == currentAction) {
                     Window.promptTextChanged.disconnect(onResourceSelected);

From 276125937c3ab37b8eec7905fd0678f302c50d60 Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Tue, 25 Sep 2018 15:18:12 -0700
Subject: [PATCH 54/56] Eliminate double equals

---
 .../marketplaceItemTester/MarketplaceItemTester.qml | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
index 8dfb277197..8f391f24c0 100644
--- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
+++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml
@@ -70,7 +70,7 @@ Rectangle {
     }
 
     function installResourceObj(resourceObj) {
-        if ("application" == resourceObj.assetType) {
+        if ("application" === resourceObj.assetType) {
             Commerce.installApp(resourceObj.resource);
         }
     }
@@ -257,10 +257,13 @@ Rectangle {
                 // Alas, there is nothing we can do about that so charge
                 // ahead as though we are sure the present signal is one
                 // we expect.
-                if ("load file" === currentAction) {
-                    Window.browseChanged.disconnect(onResourceSelected);
-                } else if ("load url" == currentAction) {
-                    Window.promptTextChanged.disconnect(onResourceSelected);
+                switch(currentAction) {
+                    case "load file":
+                        Window.browseChanged.disconnect(onResourceSelected);
+                        break
+                    case "load url":
+                        Window.promptTextChanged.disconnect(onResourceSelected);
+                        break;
                 }
                 if (resource) {
                     var resourceObj = buildResourceObj(resource);

From e6249e690e2a5ff951c02b76f2fb8ee102258fcb Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Tue, 25 Sep 2018 15:52:26 -0700
Subject: [PATCH 55/56] Remove duplicate line

---
 scripts/system/marketplaces/marketplaces.js | 1 -
 1 file changed, 1 deletion(-)

diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js
index 2e0c05be43..cc5ff99673 100644
--- a/scripts/system/marketplaces/marketplaces.js
+++ b/scripts/system/marketplaces/marketplaces.js
@@ -23,7 +23,6 @@ Script.include("/~/system/libraries/connectionUtils.js");
 var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml";
 var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml";
 var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml";
-var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml";
 var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml";
 var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml";
 var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");

From 28ec98be9e274d40edb8b83e0da89f6e1a92f10e Mon Sep 17 00:00:00 2001
From: Kerry Ivan Kurian <kkurian@gmail.com>
Date: Tue, 25 Sep 2018 19:24:46 -0700
Subject: [PATCH 56/56] Uninstall marketplace item tester on wallet shutdown

---
 scripts/system/commerce/wallet.js | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js
index 2c64b35b00..b12191b00c 100644
--- a/scripts/system/commerce/wallet.js
+++ b/scripts/system/commerce/wallet.js
@@ -521,6 +521,14 @@ function installMarketplaceItemTester() {
     });
 }
 
+function uninstallMarketplaceItemTester() {
+    if (Menu.menuExists(DEVELOPER_MENU) &&
+        Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)
+    ) {
+        Menu.removeMenuItem(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL);
+    }
+}
+
 var BUTTON_NAME = "WALLET";
 var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml";
 var ui;
@@ -555,6 +563,7 @@ function off() {
 function shutdown() {
     GlobalServices.myUsernameChanged.disconnect(onUsernameChanged);
     deleteSendMoneyParticleEffect();
+    uninstallMarketplaceItemTester();
     off();
 }