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 @@ + @@ -75,6 +76,15 @@ android:enabled="true" android:exported="false" android:process=":breakpad_uploader"/> + + + + + + 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 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 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..5645912d73 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java @@ -0,0 +1,18 @@ +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); + 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" /> + 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 @@ tagFragmentLogin tagFragmentPolicy tagFragmentPeople + tagSettings + Settings + AEC + Acoustic Echo Cancellation + Developer 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 @@ + + + + + + \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index a6de0d469c..aa7aa399b2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -72,17 +72,17 @@ 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='f312c47cd8b8dbca824c32af4eec5e66' -def qtVersionId='nyCGcb91S4QbYeJhUkawO5x1lrLdSNB_' +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.tgz' - qtChecksum='a0c8b394aec5b0fcd46714ca3a53278a' - qtVersionId='QNa.lwNJaPc0eGuIL.xZ8ebeTuLL7rh8' + qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz' + qtChecksum='c83cc477c08a892e00c71764dca051a0' + qtVersionId='OxBD7iKINv1HbyOXmAmDrBb8AF3N.Kup' } 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 = [ diff --git a/interface/resources/qml/AnimStats.qml b/interface/resources/qml/AnimStats.qml index 35ed3799a6..b1900cf0a7 100644 --- a/interface/resources/qml/AnimStats.qml +++ b/interface/resources/qml/AnimStats.qml @@ -48,35 +48,11 @@ Item { spacing: 4; x: 4; y: 4; StatText { - text: "State Machines:---------------------------------------------------------------------------" + text: root.positionText } - 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: "Anim Vars:--------------------------------------------------------------------------------" } - ListView { width: secondCol.width height: root.animVars.length * 15 @@ -104,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.animStateMachines.length > 0; + model: root.animStateMachines + delegate: StatText { + text: { + return modelData; + } + } + } + + } + } + Rectangle { width: thirdCol.width + 8 height: thirdCol.height + 8 @@ -113,10 +119,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/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml new file mode 100644 index 0000000000..8f391f24c0 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -0,0 +1,290 @@ +// +// 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 1.4 +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 + + property string installedApps + property var nextResourceObjectId: 0 + signal sendToScript(var message) + + HifiStylesUit.HifiConstants { id: hifi } + ListModel { id: resourceListModel } + + 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 "nextObjectIdInTest": + nextResourceObjectId = message.id; + spinner.visible = false; + break; + } + } + + function buildResourceObj(resource) { + resource = resource.trim(); + var assetType = (resource.match(/\.app\.json$/) ? "application" : + resource.match(/\.fst$/) ? "avatar" : + resource.match(/\.json\.gz$/) ? "content set" : + resource.match(/\.json$/) ? "entity or wearable" : + "unknown"); + return { "id": nextResourceObjectId++, + "resource": resource, + "assetType": assetType }; + } + + function installResourceObj(resourceObj) { + if ("application" === resourceObj.assetType) { + Commerce.installApp(resourceObj.resource); + } + } + + 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])); + } + } + } + + function toUrl(resource) { + var httpPattern = /^http/i; + return httpPattern.test(resource) ? resource : "file:///" + resource; + } + + function rezEntity(resource, entityType) { + sendToScript({ + method: 'tester_rezClicked', + itemHref: toUrl(resource), + itemType: entityType}); + } + + ListView { + anchors.fill: parent + anchors.leftMargin: 12 + anchors.bottomMargin: 40 + anchors.rightMargin: 12 + model: resourceListModel + spacing: 5 + interactive: false + + delegate: RowLayout { + anchors.left: parent.left + width: parent.width + spacing: 5 + + property var actions: { + "forward": function(resource, assetType){ + switch(assetType) { + case "application": + Commerce.openApp(resource); + break; + case "avatar": + MyAvatar.useFullAvatarURL(resource); + break; + case "content set": + urlHandler.handleUrl("hifi://localhost/0,0,0"); + Commerce.replaceContentSet(toUrl(resource), ""); + break; + case "entity": + case "wearable": + rezEntity(resource, assetType); + break; + default: + print("Marketplace item tester unsupported assetType " + assetType); + } + }, + "trash": function(){ + if ("application" === assetType) { + Commerce.uninstallApp(resource); + } + sendToScript({ + method: "tester_deleteResourceObject", + objectId: resourceListModel.get(index).id}); + resourceListModel.remove(index); + } + } + + Column { + Layout.preferredWidth: root.width * .6 + 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 + } + } + + ComboBox { + id: comboBox + + Layout.preferredWidth: root.width * .2 + + model: [ + "application", + "avatar", + "content set", + "entity", + "wearable", + "unknown" + ] + + currentIndex: (("entity or wearable" === assetType) ? + model.indexOf("unknown") : model.indexOf(assetType)) + + Component.onCompleted: { + onCurrentIndexChanged.connect(function() { + assetType = model[currentIndex]; + sendToScript({ + method: "tester_updateResourceObjectAssetType", + objectId: resourceListModel.get(index)["id"], + assetType: assetType }); + }); + } + } + + Repeater { + model: [ "forward", "trash" ] + + HifiStylesUit.HiFiGlyphs { + 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 { + anchors.fill: parent + onClicked: { + actions[modelData](resource, comboBox.currentText); + } + } + } + } + } + + 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 + property var actions: { + "Load File": function(){ + rootActions.currentAction = "load file"; + Window.browseChanged.connect(onResourceSelected); + 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"; + 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. + // Alas, there is nothing we can do about that so charge + // ahead as though we are sure the present signal is one + // we expect. + switch(currentAction) { + case "load file": + Window.browseChanged.disconnect(onResourceSelected); + break + case "load url": + Window.promptTextChanged.disconnect(onResourceSelected); + break; + } + if (resource) { + var resourceObj = buildResourceObj(resource); + installResourceObj(resourceObj); + sendToScript({ + method: 'tester_newResourceObject', + resourceObject: resourceObj }); + } + } + + 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/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif b/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif new file mode 100644 index 0000000000..00f75ae62f Binary files /dev/null and b/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif differ 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; diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp index 419382f2cb..400085a62a 100644 --- a/interface/src/AndroidHelper.cpp +++ b/interface/src/AndroidHelper.cpp @@ -10,6 +10,7 @@ // #include "AndroidHelper.h" #include +#include #include "Application.h" #if defined(qApp) @@ -18,6 +19,7 @@ #define qApp (static_cast(QCoreApplication::instance())) AndroidHelper::AndroidHelper() { + qRegisterMetaType("QAudio::Mode"); } AndroidHelper::~AndroidHelper() { @@ -56,3 +58,12 @@ void AndroidHelper::processURL(const QString &url) { qApp->acceptURL(url); } } + +void AndroidHelper::notifyHeadsetOn(bool pluggedIn) { +#if defined (Q_OS_ANDROID) + auto audioClient = DependencyManager::get(); + if (audioClient) { + QMetaObject::invokeMethod(audioClient.data(), "setHeadsetPluggedIn", Q_ARG(bool, pluggedIn)); + } +#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/interface/src/Application.cpp b/interface/src/Application.cpp index 46cebc1661..3a34c87ef1 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()->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; @@ -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 @@ -2881,9 +2883,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" }, @@ -3498,13 +3501,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()->getDomainHandler().getInterstitialModeEnabled(); + if (enableInterstitial) { + if (_interstitialMode != interstitialMode) { + _interstitialMode = interstitialMode; - DependencyManager::get()->setAudioPaused(_interstitialMode); - DependencyManager::get()->setMyAvatarDataPacketsPaused(_interstitialMode); + DependencyManager::get()->setAudioPaused(_interstitialMode); + DependencyManager::get()->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 3c85cfb339..e86061b090 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -41,9 +41,15 @@ void ConnectionMonitor::init() { } connect(&_timer, &QTimer::timeout, this, [this]() { - qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; // set in a timeout error - emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5); + bool enableInterstitial = DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled(); + if (enableInterstitial) { + qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; + emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, "", 5); + } else { + qDebug() << "ConnectionMonitor: Showing connection failure window"; + DependencyManager::get()->setDomainConnectionFailureVisibility(true); + } }); } @@ -53,4 +59,8 @@ void ConnectionMonitor::startTimer() { void ConnectionMonitor::stopTimer() { _timer.stop(); + bool enableInterstitial = DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled(); + if (!enableInterstitial) { + DependencyManager::get()->setDomainConnectionFailureVisibility(false); + } } diff --git a/interface/src/ConnectionMonitor.h b/interface/src/ConnectionMonitor.h index 5e75e2618b..2fda6ef7cd 100644 --- a/interface/src/ConnectionMonitor.h +++ b/interface/src/ConnectionMonitor.h @@ -24,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,4 +34,4 @@ private: QTimer _timer; }; -#endif // hifi_ConnectionMonitor_h \ No newline at end of file +#endif // hifi_ConnectionMonitor_h diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 1faf17ea9a..0fdd246d7b 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -456,31 +456,37 @@ 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(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 removedAvatars; + + { + QWriteLocker locker(&_hashLock); + + removedAvatars.reserve(_avatarHash.size()); + + auto avatarIterator = _avatarHash.begin(); + while (avatarIterator != _avatarHash.end()) { + auto avatar = std::static_pointer_cast(avatarIterator.value()); + if (avatar != _myAvatar) { + removedAvatars.push_back(avatar); + avatarIterator = _avatarHash.erase(avatarIterator); + } else { + ++avatarIterator; + } + } + } + + for (auto& av : removedAvatars) { + handleRemovedAvatar(av); + } } 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(avatarIterator.value()); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 306ba6f39b..9c4287728d 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -204,7 +204,12 @@ private: void simulateAvatarFades(float deltaTime); AvatarSharedPointer newSharedAvatar() override; - void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; + + // 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; QVector _avatarsToFade; 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(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 idHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" + << QString::number(avatarEntityIndex + 1) << "id", QUuid()); + _avatarEntityIDSettings.push_back(idHandle); + Setting::Handle 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(); _avatarEntitiesLock.withReadLock([&] { - for (auto entityID : _avatarEntityData.keys()) { - if (hmdInterface->getCurrentTabletFrameID() == entityID) { - // don't persist the tablet between domains / sessions - continue; - } + QList avatarEntityIDs = _avatarEntityData.keys(); + unsigned int avatarEntityCount = avatarEntityIDs.size(); + unsigned int previousAvatarEntityCount = _avatarEntityCountSetting.get(0); + resizeAvatarEntitySettingHandles(std::max(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 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 _dominantHandSetting; + Setting::Handle _headPitchSetting; + Setting::Handle _scaleSetting; + Setting::Handle _yawSpeedSetting; + Setting::Handle _pitchSpeedSetting; + Setting::Handle _fullAvatarURLSetting; + Setting::Handle _fullAvatarModelNameSetting; + Setting::Handle _animGraphURLSetting; + Setting::Handle _displayNameSetting; + Setting::Handle _collisionSoundURLSetting; + Setting::Handle _useSnapTurnSetting; + Setting::Handle _userHeightSetting; + Setting::Handle _flyingHMDSetting; + Setting::Handle _avatarEntityCountSetting; + std::vector> _avatarEntityIDSettings; + std::vector> _avatarEntityDataSettings; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 06da18148f..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->updateLocation(certificateID, DependencyManager::get()->getPlaceName(), true); + if (!certificateID.isEmpty()) { + auto ledger = DependencyManager::get(); + ledger->updateLocation( + certificateID, + DependencyManager::get()->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); } @@ -228,6 +234,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/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 5fbe3a90b5..3c66923b4e 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -35,6 +35,14 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& } } +PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + auto rayPickResult = std::dynamic_pointer_cast(pickResult); + if (!rayPickResult) { + return std::make_shared(); + } + return std::make_shared(*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 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 ad698c409b..33fa8738d9 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -30,6 +30,14 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& { } +PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + auto parabolaPickResult = std::dynamic_pointer_cast(pickResult); + if (!parabolaPickResult) { + return std::make_shared(); + } + return std::make_shared(*parabolaPickResult.get()); +} + void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { auto renderState = std::static_pointer_cast(_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 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..b648e125bf 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -147,6 +147,14 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { return false; } +PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + auto stylusPickResult = std::dynamic_pointer_cast(pickResult); + if (!stylusPickResult) { + return std::make_shared(); + } + return std::make_shared(*stylusPickResult.get()); +} + Pointer::PickedObject StylusPointer::getHoveredObject(const PickResultPointer& pickResult) { auto stylusPickResult = std::static_pointer_cast(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/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(LAST_BROWSE_ASSETS_LOCATION_SETTING).set(location); } +bool WindowScriptingInterface::getInterstitialModeEnabled() const { + return DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled(); +} + +void WindowScriptingInterface::setInterstitialModeEnabled(bool enableInterstitialMode) { + DependencyManager::get()->getDomainHandler().setInterstitialModeEnabled(enableInterstitialMode); +} + bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) { auto offscreenUi = DependencyManager::get(); 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/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 _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 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 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)); } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 3a33eccc8a..d00bc29054 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 #endif @@ -101,6 +100,13 @@ QList 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); @@ -210,6 +216,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 @@ -278,9 +285,6 @@ void AudioClient::customDeleter() { _shouldRestartInputSetup = false; #endif stop(); - _checkDevicesTimer->stop(); - _checkPeakValuesTimer->stop(); - deleteLater(); } @@ -461,9 +465,14 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { #if defined (Q_OS_ANDROID) if (mode == QAudio::AudioInput) { + Setting::Handle enableAEC(SETTING_AEC_KEY, false); + bool aecEnabled = enableAEC.get(); + auto audioClient = DependencyManager::get(); + bool headsetOn = audioClient? audioClient->isHeadsetPluggedIn() : false; auto inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); for (auto inputDevice : inputDevices) { - if (inputDevice.deviceName() == VOICE_RECOGNITION) { + if (((headsetOn || !aecEnabled) && inputDevice.deviceName() == VOICE_RECOGNITION) || + ((!headsetOn && aecEnabled) && inputDevice.deviceName() == VOICE_COMMUNICATION)) { return inputDevice; } } @@ -648,12 +657,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); @@ -1640,6 +1663,29 @@ void AudioClient::checkInputTimeout() { #endif } +void AudioClient::setHeadsetPluggedIn(bool pluggedIn) { +#if defined(Q_OS_ANDROID) + if (pluggedIn == !_isHeadsetPluggedIn && !_inputDeviceInfo.isNull()) { + QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField("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 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 4640d7c045..5e7f1fb8a0 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -64,6 +64,13 @@ #pragma warning( pop ) #endif +#if defined (Q_OS_ANDROID) +#define VOICE_RECOGNITION "voicerecognition" +#define VOICE_COMMUNICATION "voicecommunication" + +#define SETTING_AEC_KEY "Android/aec" +#endif + class QAudioInput; class QAudioOutput; class QIODevice; @@ -169,6 +176,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(); @@ -217,6 +228,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); @@ -278,6 +292,7 @@ private: #ifdef Q_OS_ANDROID QTimer _checkInputTimer; long _inputReadsSinceLastCheck = 0l; + bool _isHeadsetPluggedIn; #endif class Gate { @@ -432,7 +447,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); 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. diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index d205a915f8..01557e307e 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 AvatarReplicas::takeReplicas(const QUuid& parentID) { + std::vector replicas; + + auto it = _replicasMap.find(parentID); + + if (it != _replicasMap.end()) { + // take a copy of the replica shared pointers for this parent + replicas.swap(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 message, S } void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) { - QWriteLocker locker(&_hashLock); + std::vector 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 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..c2cb448e52 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 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, @@ -179,7 +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); - + virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason); AvatarHash _avatarHash; 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()) { 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: * * @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..df34a1fb59 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -15,6 +15,10 @@ #include +#include + +#include + #include #include @@ -134,6 +138,18 @@ void DomainHandler::hardReset() { _pendingPath.clear(); } +bool DomainHandler::getInterstitialModeEnabled() const { + return _interstitialModeSettingLock.resultWithReadLock([&] { + 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 namedPaths) { DependencyManager::get()->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() { @@ -485,13 +505,9 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer #include +#include +#include + #include "HifiSockAddr.h" #include "NetworkPeer.h" #include "NLPacket.h" @@ -83,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 namedPaths); @@ -171,7 +176,7 @@ public slots: void processDomainServerConnectionDeniedPacket(QSharedPointer 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; } @@ -224,6 +229,8 @@ private: QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; + mutable ReadWriteLockable _interstitialModeSettingLock; + Setting::Handle _enableInterstitialMode{ "enableInterstitialMode", false }; QSet _domainConnectionRefusals; bool _hasCheckedForAccessToken { false }; diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 031baece5f..26460cbdd7 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -68,7 +68,8 @@ 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); + // Pointer needs its own PickResult object so it doesn't modify the cached pick result + 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; 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 skinnedShapeKeys{}; + std::vector skinnedShapeKeys; + std::vector 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(); @@ -548,16 +565,4 @@ const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const const auto selectedMetasAndOpaques = task.addJob("OpaqueSelection", selectMetaAndOpaqueInput); const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, selectionName).asVarying(); return task.addJob("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); }; 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& 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)); 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([&] { + return _qSettings.fileName(); + }); + } + + void Manager::remove(const QString &key) { + withWriteLock([&] { + _qSettings.remove(key); + }); + } + + QStringList Manager::childGroups() const { + return resultWithReadLock([&] { + return _qSettings.childGroups(); + }); + } + + QStringList Manager::childKeys() const { + return resultWithReadLock([&] { + return _qSettings.childKeys(); + }); + } + + QStringList Manager::allKeys() const { + return resultWithReadLock([&] { + return _qSettings.allKeys(); + }); + } + + bool Manager::contains(const QString &key) const { + return resultWithReadLock([&] { + return _qSettings.contains(key); + }); + } + + int Manager::beginReadArray(const QString &prefix) { + return resultWithReadLock([&] { + 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([&] { + 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; }; } diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index bb22a1e753..012e7aa1f5 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -19,8 +19,8 @@ #include #include #include -#include #include +#include #include @@ -127,82 +127,10 @@ void usecTimestampNowForceClockSkew(qint64 clockSkew) { ::usecTimestampNowAdjust = clockSkew; } -static std::atomic 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(system_clock::now() - unixEpoch).count() + usecTimestampNowAdjust; } float secTimestampNow() { diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 89bcc97444..5b91afea33 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -529,6 +529,35 @@ function isReturnedDataEmpty(data) { return historyArray.length === 0; } +var DEVELOPER_MENU = "Developer"; +var MARKETPLACE_ITEM_TESTER_LABEL = "Marketplace 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) { + ui.open(MARKETPLACE_ITEM_TESTER_QML_SOURCE); + } + }); +} + +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"; @@ -549,7 +578,9 @@ function startup() { notificationPollCaresAboutSince: true }); GlobalServices.myUsernameChanged.connect(onUsernameChanged); + installMarketplaceItemTester(); } + var isUpdateOverlaysWired = false; function off() { Users.usernameFromIDReply.disconnect(usernameFromIDReply); @@ -564,9 +595,11 @@ function off() { } removeOverlays(); } + function shutdown() { GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); deleteSendMoneyParticleEffect(); + uninstallMarketplaceItemTester(); off(); } 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. 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/html/js/entityList.js b/scripts/system/html/js/entityList.js index 23de49b613..7cc5937536 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -164,7 +164,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 = ''; + } } }); @@ -388,15 +391,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; 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)) { 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; } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 27fa929390..85cd499d20 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -20,13 +20,14 @@ var AppUi = Script.require('appUi'); Script.include("/~/system/libraries/gridTool.js"); Script.include("/~/system/libraries/connectionUtils.js"); -var METAVERSE_SERVER_URL = Account.metaverseServerURL; -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_INSPECTIONCERTIFICATE_QML_PATH = "hifi/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_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; -var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "hifi/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")); // Event bridge messages. @@ -755,7 +756,7 @@ function deleteSendAssetParticleEffect() { } sendAssetRecipient = null; } - + var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview"); var UI_FADE_TIMEOUT_MS = 150; function maybeEnableHMDPreview() { @@ -767,6 +768,13 @@ function maybeEnableHMDPreview() { }, UI_FADE_TIMEOUT_MS); } +var resourceObjectsInTest = []; +function signalNewResourceObjectInTest(resourceObject) { + ui.tablet.sendToQml({ + method: "newResourceObjectInTest", + resourceObject: resourceObject }); +} + var onQmlMessageReceived = function onQmlMessageReceived(message) { if (message.messageSrc === "HTML") { return; @@ -816,8 +824,20 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { break; case 'checkout_rezClicked': case 'purchases_rezClicked': + case 'tester_rezClicked': rezEntity(message.itemHref, message.itemType); break; + case 'tester_newResourceObject': + var resourceObject = message.resourceObject; + resourceObjectsInTest[resourceObject.id] = resourceObject; + signalNewResourceObjectInTest(resourceObject); + break; + case 'tester_updateResourceObjectAssetType': + resourceObjectsInTest[message.objectId].assetType = message.assetType; + break; + case 'tester_deleteResourceObject': + delete resourceObjectsInTest[message.objectId]; + break; case 'header_marketplaceImageClicked': case 'purchases_backClicked': ui.open(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); @@ -840,10 +860,6 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { openLoginWindow(); break; case 'disableHmdPreview': - if (!savedDisablePreviewOption) { - savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview"); - } - if (!savedDisablePreviewOption) { DesktopPreviewProvider.setPreviewDisabledReason("SECURE_SCREEN"); Menu.setIsOptionChecked("Disable Preview", true); @@ -960,34 +976,65 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { } }; +function pushResourceObjectsInTest() { + 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() // // Description: // -Called when the TabletScriptingInterface::screenChanged() signal is emitted. The "type" argument can be either the string // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. -var onMarketplaceScreen = false; -var onWalletScreen = false; + var onCommerceScreen = false; var onInspectionCertificateScreen = false; +var onMarketplaceItemTesterScreen = false; +var onMarketplaceScreen = false; +var onWalletScreen = false; var onTabletScreenChanged = function onTabletScreenChanged(type, url) { ui.setCurrentVisibleScreenMetadata(type, url); onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; onInspectionCertificateScreen = type === "QML" && url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -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 || - onInspectionCertificateScreen); + 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); - // exiting wallet or commerce screen - if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { + if ((!onWalletScreenNow && onWalletScreen) || + (!onCommerceScreenNow && onCommerceScreen) || + (!onMarketplaceItemTesterScreenNow && onMarketplaceScreen) + ) { + // exiting wallet, commerce, or marketplace item tester screen maybeEnableHMDPreview(); } onCommerceScreen = onCommerceScreenNow; onWalletScreen = onWalletScreenNow; - wireQmlEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); + onMarketplaceItemTesterScreen = onMarketplaceItemTesterScreenNow; + + wireQmlEventBridge( + onMarketplaceScreen || + onCommerceScreen || + onWalletScreen || + onMarketplaceItemTesterScreen); if (url === MARKETPLACE_PURCHASES_QML_PATH) { + // FIXME? Is there a race condition here in which the event + // bridge may not be up yet? If so, Script.setTimeout(..., 750) + // may help avoid the condition. ui.tablet.sendToQml({ method: 'updatePurchases', referrerURL: referrerURL, @@ -1024,6 +1071,14 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { }); off(); } + + if (onMarketplaceItemTesterScreen) { + // Why setTimeout? The QML event bridge, wired above, requires a + // variable amount of time to come up, in practice less than + // 750ms. + Script.setTimeout(pushResourceObjectsInTest, 750); + } + console.debug(ui.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type + "\nNew screen URL: " + url + "\nCurrent app open status: " + ui.isOpen + "\n"); };