diff --git a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java index b1e416780..0bdff7af4 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java @@ -1274,6 +1274,11 @@ public void openNewWindow(String uri) { newWindow.getSessionStack().newSessionWithUrl(uri); } + @Override + public WindowWidget getFocusedWindow() { + return mWindows.getFocusedWindow(); + } + private native void addWidgetNative(int aHandle, WidgetPlacement aPlacement); private native void updateWidgetNative(int aHandle, WidgetPlacement aPlacement); private native void updateVisibleWidgetsNative(); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/browser/SettingsStore.java b/app/src/common/shared/org/mozilla/vrbrowser/browser/SettingsStore.java index d923a828a..067ec7180 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/browser/SettingsStore.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/browser/SettingsStore.java @@ -52,6 +52,7 @@ SettingsStore getInstance(final @NonNull Context aContext) { public final static boolean TRACKING_DEFAULT = true; public final static boolean NOTIFICATIONS_DEFAULT = true; public final static boolean SPEECH_DATA_COLLECTION_DEFAULT = false; + public final static boolean SPEECH_DATA_COLLECTION_REVIEWED_DEFAULT = false; public final static boolean SERVO_DEFAULT = false; public final static int UA_MODE_DEFAULT = GeckoSessionSettings.USER_AGENT_MODE_VR; public final static int INPUT_MODE_DEFAULT = 1; @@ -542,5 +543,16 @@ public void setNotificationsEnabled(boolean isEnabled) { editor.putBoolean(mContext.getString(R.string.settings_key_notifications), isEnabled); editor.commit(); } + + public boolean isSpeechDataCollectionReviewed() { + return mPrefs.getBoolean( + mContext.getString(R.string.settings_key_speech_data_collection_reviewed), SPEECH_DATA_COLLECTION_REVIEWED_DEFAULT); + } + + public void setSpeechDataCollectionReviewed(boolean isEnabled) { + SharedPreferences.Editor editor = mPrefs.edit(); + editor.putBoolean(mContext.getString(R.string.settings_key_speech_data_collection_reviewed), isEnabled); + editor.commit(); + } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java index 889cac8e9..79ebd778f 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/NavigationBarWidget.java @@ -574,7 +574,7 @@ private void enterVRVideo(@VideoProjectionMenuWidget.VideoProjectionFlags int aP this.setVisible(false); if (mFullScreenMedia != null && mFullScreenMedia.getWidth() > 0 && mFullScreenMedia.getHeight() > 0) { final boolean resetBorder = aProjection == VideoProjectionMenuWidget.VIDEO_PROJECTION_360 || - aProjection == VideoProjectionMenuWidget.VIDEO_PROJECTION_360_STEREO; + aProjection == VideoProjectionMenuWidget.VIDEO_PROJECTION_360_STEREO; mAttachedWindow.enableVRVideoMode(mFullScreenMedia.getWidth(), mFullScreenMedia.getHeight(), resetBorder); // Handle video resize while in VR video playback mFullScreenMedia.setResizeDelegate((width, height) -> { @@ -844,6 +844,7 @@ public void OnVoiceSearchClicked() { mVoiceSearchWidget.hide(REMOVE_WIDGET); } else { + mVoiceSearchWidget.getPlacement().parentHandle = mAttachedWindow.getHandle(); mVoiceSearchWidget.show(REQUEST_FOCUS); } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WidgetManagerDelegate.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WidgetManagerDelegate.java index 536f6c864..c406bb5da 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WidgetManagerDelegate.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WidgetManagerDelegate.java @@ -79,4 +79,5 @@ interface WorldClickListener { boolean isPermissionGranted(@NonNull String permission); void requestPermission(String uri, @NonNull String permission, GeckoSession.PermissionDelegate.Callback aCallback); void openNewWindow(@NonNull String uri); + WindowWidget getFocusedWindow(); } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java index c76651aa1..62ccb38c2 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/WindowWidget.java @@ -22,6 +22,7 @@ import org.mozilla.gecko.util.ThreadUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import org.mozilla.geckoview.AllowOrDeny; import org.mozilla.geckoview.GeckoDisplay; @@ -35,6 +36,7 @@ import org.mozilla.vrbrowser.browser.engine.SessionStack; import org.mozilla.vrbrowser.telemetry.TelemetryWrapper; import org.mozilla.vrbrowser.ui.views.BookmarksView; +import org.mozilla.vrbrowser.ui.widgets.dialogs.AppDialogWidget; import org.mozilla.vrbrowser.ui.widgets.dialogs.ContextMenuWidget; import org.mozilla.vrbrowser.ui.widgets.dialogs.MaxWindowsWidget; import org.mozilla.vrbrowser.ui.widgets.prompts.AlertPromptWidget; @@ -72,6 +74,7 @@ public class WindowWidget extends UIWidget implements SessionChangeListener, private TextPromptWidget mTextPrompt; private AuthPromptWidget mAuthPrompt; private NoInternetWidget mNoInternetToast; + private AppDialogWidget mAppDialog; private ContextMenuWidget mContextMenu; private int mWidthBackup; private int mHeightBackup; @@ -728,6 +731,26 @@ public void showButtonPrompt(String title, @NonNull String msg, @NonNull String[ mConfirmPrompt.show(REQUEST_FOCUS); } + public void showAppDialog(@NonNull @StringRes int title, @NonNull @StringRes int description, @NonNull @StringRes int [] btnMsg, @NonNull AppDialogWidget.Delegate callback) { + mAppDialog = new AppDialogWidget(getContext()); + mAppDialog.mWidgetPlacement.parentHandle = getHandle(); + mAppDialog.setTitle(title); + mAppDialog.setMessage(description); + mAppDialog.setButtons(btnMsg); + mAppDialog.setDelegate(callback); + mAppDialog.show(REQUEST_FOCUS); + } + + public void showAppDialog(@NonNull String title, @NonNull String description, @NonNull String[] btnMsg, @NonNull AppDialogWidget.Delegate callback) { + mAppDialog = new AppDialogWidget(getContext()); + mAppDialog.mWidgetPlacement.parentHandle = getHandle(); + mAppDialog.setTitle(title); + mAppDialog.setMessage(description); + mAppDialog.setButtons(btnMsg); + mAppDialog.setDelegate(callback); + mAppDialog.show(REQUEST_FOCUS); + } + public void showMaxWindowsDialog(int maxDialogs) { mMaxWindowsDialog = new MaxWindowsWidget(getContext()); mMaxWindowsDialog.mWidgetPlacement.parentHandle = getHandle(); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/AppDialogWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/AppDialogWidget.java new file mode 100644 index 000000000..55d8deee1 --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/AppDialogWidget.java @@ -0,0 +1,159 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.vrbrowser.ui.widgets.dialogs; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewTreeObserver; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.databinding.DataBindingUtil; + +import org.mozilla.vrbrowser.R; +import org.mozilla.vrbrowser.databinding.AppDialogBinding; +import org.mozilla.vrbrowser.ui.widgets.WidgetManagerDelegate; +import org.mozilla.vrbrowser.ui.widgets.WidgetPlacement; +import org.mozilla.vrbrowser.utils.ViewUtils; + +public class AppDialogWidget extends UIDialog { + + public interface Delegate { + void onButtonClicked(int index); + void onMessageLinkClicked(@NonNull String url); + } + + public static final int LEFT = 0; + public static final int RIGHT = 1; + + private AppDialogBinding mBinding; + private Delegate mAppDialogDelegate; + + public AppDialogWidget(Context aContext) { + super(aContext); + initialize(aContext); + } + + public AppDialogWidget(Context aContext, AttributeSet aAttrs) { + super(aContext, aAttrs); + initialize(aContext); + } + + public AppDialogWidget(Context aContext, AttributeSet aAttrs, int aDefStyle) { + super(aContext, aAttrs, aDefStyle); + initialize(aContext); + } + + private void initialize(Context aContext) { + LayoutInflater inflater = LayoutInflater.from(aContext); + + // Inflate this data binding layout + mBinding = DataBindingUtil.inflate(inflater, R.layout.app_dialog, this, true); + + mBinding.leftButton.setOnClickListener(v -> { + if (mAppDialogDelegate != null) + mAppDialogDelegate.onButtonClicked(LEFT); + + AppDialogWidget.this.onDismiss(); + }); + mBinding.rightButton.setOnClickListener(v -> { + if (mAppDialogDelegate != null) + mAppDialogDelegate.onButtonClicked(RIGHT); + + AppDialogWidget.this.onDismiss(); + }); + } + + @Override + protected void initializeWidgetPlacement(WidgetPlacement aPlacement) { + aPlacement.visible = false; + aPlacement.width = WidgetPlacement.dpDimension(getContext(), R.dimen.app_dialog_width); + aPlacement.height = WidgetPlacement.pixelDimension(getContext(), R.dimen.browser_width_pixels)/2; + aPlacement.parentAnchorX = 0.5f; + aPlacement.parentAnchorY = 0.5f; + aPlacement.anchorX = 0.5f; + aPlacement.anchorY = 0.5f; + aPlacement.translationZ = WidgetPlacement.unitFromMeters(getContext(), R.dimen.app_dialog_z_distance); + } + + + @Override + public void show(@ShowFlags int aShowFlags) { + measure(View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + + super.show(aShowFlags); + + mWidgetManager.pushWorldBrightness(this, WidgetManagerDelegate.DEFAULT_DIM_BRIGHTNESS); + + ViewTreeObserver viewTreeObserver = getViewTreeObserver(); + if (viewTreeObserver.isAlive()) { + viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + getViewTreeObserver().removeOnGlobalLayoutListener(this); + mWidgetPlacement.height = (int)(getHeight()/mWidgetPlacement.density); + mWidgetManager.updateWidget(AppDialogWidget.this); + } + }); + } + } + + public void hide(@HideFlags int aHideFlags) { + super.hide(aHideFlags); + mWidgetManager.popWorldBrightness(this); + } + + // WidgetManagerDelegate.FocusChangeListener + @Override + public void onGlobalFocusChanged(View oldFocus, View newFocus) { + if (oldFocus == this && isVisible() && findViewById(newFocus.getId()) == null) { + onDismiss(); + } + } + + public void setDelegate(Delegate delegate) { + mAppDialogDelegate = delegate; + } + + public void setTitle(@StringRes int title) { + mBinding.title.setText(title); + } + + public void setTitle(String title) { + mBinding.title.setText(title); + } + + public void setMessage(@StringRes int message) { + ViewUtils.setTextViewHTML(mBinding.message, getResources().getString(message), (widget, url) -> { + if (mAppDialogDelegate != null) { + mAppDialogDelegate.onMessageLinkClicked(url); + onDismiss(); + } + }); + } + + public void setMessage(String message) { + mBinding.message.setText(message); + } + + public void setButtons(@StringRes int[] buttons) { + if (buttons.length > 0) + mBinding.leftButton.setText(buttons[LEFT]); + if (buttons.length > 1) + mBinding.rightButton.setText(buttons[RIGHT]); + } + + public void setButtons(@NonNull String[] buttons) { + if (buttons.length > 0) + mBinding.leftButton.setText(buttons[LEFT]); + if (buttons.length > 1) + mBinding.rightButton.setText(buttons[RIGHT]); + } + +} diff --git a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java index 68aac8488..a62d501e1 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/ui/widgets/dialogs/VoiceSearchWidget.java @@ -23,15 +23,18 @@ import com.mozilla.speechlibrary.MozillaSpeechService; import com.mozilla.speechlibrary.STTResult; +import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.vrbrowser.R; import org.mozilla.vrbrowser.audio.AudioEngine; import org.mozilla.vrbrowser.browser.SettingsStore; +import org.mozilla.vrbrowser.browser.engine.SessionStore; import org.mozilla.vrbrowser.utils.DeviceType; import org.mozilla.vrbrowser.ui.views.UIButton; import org.mozilla.vrbrowser.ui.widgets.WidgetManagerDelegate; import org.mozilla.vrbrowser.ui.widgets.WidgetPlacement; import org.mozilla.vrbrowser.utils.LocaleUtils; +import androidx.annotation.NonNull; import androidx.core.app.ActivityCompat; public class VoiceSearchWidget extends UIDialog implements WidgetManagerDelegate.PermissionListener, @@ -90,9 +93,6 @@ private void initialize(Context aContext) { mMozillaSpeechService = MozillaSpeechService.getInstance(); mMozillaSpeechService.setProductTag(getContext().getString(R.string.voice_app_id)); - boolean storeData = SettingsStore.getInstance(aContext).isSpeechDataCollectionEnabled(); - mMozillaSpeechService.storeSamples(storeData); - mMozillaSpeechService.storeTranscriptions(storeData); mVoiceSearchText1 = findViewById(R.id.voiceSearchText1); mVoiceSearchText2 = findViewById(R.id.voiceSearchText2); @@ -150,8 +150,7 @@ protected void initializeWidgetPlacement(WidgetPlacement aPlacement) { aPlacement.parentAnchorY = 0.5f; aPlacement.anchorX = 0.5f; aPlacement.anchorY = 0.5f; - aPlacement.translationY = WidgetPlacement.unitFromMeters(getContext(), R.dimen.restart_dialog_world_y); - aPlacement.translationZ = WidgetPlacement.unitFromMeters(getContext(), R.dimen.restart_dialog_world_z); + aPlacement.translationZ = WidgetPlacement.unitFromMeters(getContext(), R.dimen.voice_search_world_z); } public void setPlacementForKeyboard(int aHandle) { @@ -230,6 +229,11 @@ public void startVoiceSearch() { } else { String locale = LocaleUtils.getVoiceSearchLocale(getContext()); mMozillaSpeechService.setLanguage(LocaleUtils.mapToMozillaSpeechLocales(locale)); + boolean storeData = SettingsStore.getInstance(getContext()).isSpeechDataCollectionEnabled(); + if (SessionStore.get().getActiveStore().isPrivateMode()) + storeData = false; + mMozillaSpeechService.storeSamples(storeData); + mMozillaSpeechService.storeTranscriptions(storeData); mMozillaSpeechService.start(getContext().getApplicationContext()); mIsSpeechRecognitionRunning = true; } @@ -269,11 +273,37 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in @Override public void show(@ShowFlags int aShowFlags) { - super.show(aShowFlags); + if (SettingsStore.getInstance(getContext()).isSpeechDataCollectionReviewed()) { + super.show(aShowFlags); - setStartListeningState(); + setStartListeningState(); - startVoiceSearch(); + startVoiceSearch(); + + } else { + mWidgetManager.getFocusedWindow().showAppDialog( + R.string.voice_samples_collect_dialog_title, + R.string.voice_samples_collect_dialog_description, + new int[]{ + R.string.voice_samples_collect_dialog_do_not_allow, + R.string.voice_samples_collect_dialog_allow}, + new AppDialogWidget.Delegate() { + @Override + public void onButtonClicked(int index) { + SettingsStore.getInstance(getContext()).setSpeechDataCollectionReviewed(true); + if (index == AppDialogWidget.RIGHT) { + SettingsStore.getInstance(getContext()).setSpeechDataCollectionEnabled(true); + } + ThreadUtils.postToUiThread(() -> show(aShowFlags)); + } + + @Override + public void onMessageLinkClicked(@NonNull String url) { + mWidgetManager.getFocusedWindow().getSessionStack().loadUri(getResources().getString(R.string.private_policy_url)); + onDismiss(); + } + }); + } } @Override diff --git a/app/src/main/res/layout/app_dialog.xml b/app/src/main/res/layout/app_dialog.xml new file mode 100644 index 000000000..29b8e30ff --- /dev/null +++ b/app/src/main/res/layout/app_dialog.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + +