diff --git a/app/src/aosp/java/com/igalia/wolvic/PlatformActivity.java b/app/src/aosp/java/com/igalia/wolvic/PlatformActivity.java index cbec98275e..c06e2bdcb2 100644 --- a/app/src/aosp/java/com/igalia/wolvic/PlatformActivity.java +++ b/app/src/aosp/java/com/igalia/wolvic/PlatformActivity.java @@ -11,6 +11,7 @@ import android.view.View; import android.view.WindowManager; +import com.igalia.wolvic.ui.widgets.WidgetManagerDelegate; import com.igalia.wolvic.utils.SystemUtils; public class PlatformActivity extends NativeActivity { @@ -30,6 +31,8 @@ public static boolean isPositionTrackingSupported() { return true; } + public final PlatformActivityPlugin createPlatformPlugin(WidgetManagerDelegate delegate) { return null; } + protected Intent getStoreIntent() { // Dummy implementation. return null; diff --git a/app/src/common/shared/com/igalia/wolvic/PlatformActivityPlugin.java b/app/src/common/shared/com/igalia/wolvic/PlatformActivityPlugin.java new file mode 100644 index 0000000000..bad5291d8c --- /dev/null +++ b/app/src/common/shared/com/igalia/wolvic/PlatformActivityPlugin.java @@ -0,0 +1,6 @@ +package com.igalia.wolvic; + +public interface PlatformActivityPlugin { + void onKeyboardVisibilityChange(boolean isVisible); + void onVideoAvailabilityChange(); +} diff --git a/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java b/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java index 030e95c86c..8e55333cb3 100644 --- a/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java +++ b/app/src/common/shared/com/igalia/wolvic/VRBrowserActivity.java @@ -245,6 +245,7 @@ public void run() { private boolean mIsPassthroughEnabled = false; private long mLastBatteryUpdate = System.nanoTime(); private int mLastBatteryLevel = -1; + private PlatformActivityPlugin mPlatformPlugin; private boolean callOnAudioManager(Consumer fn) { if (mAudioManager == null) { @@ -439,6 +440,10 @@ public void onWindowVideoAvailabilityChanged(@NonNull WindowWidget aWindow) { WidgetManagerDelegate.CPU_LEVEL_NORMAL; queueRunnable(() -> setCPULevelNative(cpuLevel)); + + if (mPlatformPlugin != null) { + mPlatformPlugin.onVideoAvailabilityChange(); + } } }); @@ -450,6 +455,9 @@ public void onWindowVideoAvailabilityChanged(@NonNull WindowWidget aWindow) { addWidgets(Arrays.asList(mRootWidget, mNavigationBar, mKeyboard, mTray, mWebXRInterstitial)); + // Create the platform plugin after widgets are created to be extra safe. + mPlatformPlugin = createPlatformPlugin(this); + mWindows.restoreSessions(); } @@ -1676,6 +1684,10 @@ public void updateWidget(final Widget aWidget) { view.setVisibility(visible ? View.VISIBLE : View.GONE); } + if (aWidget == mKeyboard && mPlatformPlugin != null) { + mPlatformPlugin.onKeyboardVisibilityChange(visible); + } + for (UpdateListener listener: mWidgetUpdateListeners) { listener.onWidgetUpdate(aWidget); } @@ -1899,6 +1911,8 @@ public void setWindowSize(float targetWidth, float targetHeight) { @Override public void keyboardDismissed() { mNavigationBar.showVoiceSearch(); + if (mPlatformPlugin != null) + mPlatformPlugin.onKeyboardVisibilityChange(false); } @Override @@ -2108,6 +2122,9 @@ public AppServicesProvider getServicesProvider() { return (AppServicesProvider)getApplication(); } + @Override + public KeyboardWidget getKeyboard() { return mKeyboard; } + 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/com/igalia/wolvic/ui/widgets/KeyboardWidget.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/KeyboardWidget.java index 7bd79d67e6..0f2845d036 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/widgets/KeyboardWidget.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/KeyboardWidget.java @@ -1496,4 +1496,9 @@ public void onSessionChanged(@NonNull Session aOldSession, @NonNull Session aSes aOldSession.removeTextInputListener(this); aSession.addTextInputListener(this); } + + public void simulateVoiceButtonClick() { + mKeyboardVoiceButton.performClick(); + } + } diff --git a/app/src/common/shared/com/igalia/wolvic/ui/widgets/WidgetManagerDelegate.java b/app/src/common/shared/com/igalia/wolvic/ui/widgets/WidgetManagerDelegate.java index 9947b7f3af..77c5aff6ed 100644 --- a/app/src/common/shared/com/igalia/wolvic/ui/widgets/WidgetManagerDelegate.java +++ b/app/src/common/shared/com/igalia/wolvic/ui/widgets/WidgetManagerDelegate.java @@ -124,4 +124,5 @@ interface WebXRListener { void updateLocale(@NonNull Context context); @NonNull AppServicesProvider getServicesProvider(); + KeyboardWidget getKeyboard(); } diff --git a/app/src/hvr/java/com/igalia/wolvic/PlatformActivity.java b/app/src/hvr/java/com/igalia/wolvic/PlatformActivity.java index ace01e7f48..02fb7e10cd 100644 --- a/app/src/hvr/java/com/igalia/wolvic/PlatformActivity.java +++ b/app/src/hvr/java/com/igalia/wolvic/PlatformActivity.java @@ -372,6 +372,8 @@ public void surfaceDestroyed(SurfaceHolder holder) queueRunnable(this::nativeOnSurfaceDestroyed); } + public final PlatformActivityPlugin createPlatformPlugin(WidgetManagerDelegate delegate) { return null; } + protected boolean platformExit() { return false; } diff --git a/app/src/lynx/java/com/igalia/wolvic/PlatformActivity.java b/app/src/lynx/java/com/igalia/wolvic/PlatformActivity.java index cbec98275e..7703d2bfa8 100644 --- a/app/src/lynx/java/com/igalia/wolvic/PlatformActivity.java +++ b/app/src/lynx/java/com/igalia/wolvic/PlatformActivity.java @@ -11,6 +11,7 @@ import android.view.View; import android.view.WindowManager; +import com.igalia.wolvic.ui.widgets.WidgetManagerDelegate; import com.igalia.wolvic.utils.SystemUtils; public class PlatformActivity extends NativeActivity { @@ -44,6 +45,9 @@ public void run() { } }); } + + public final PlatformActivityPlugin createPlatformPlugin(WidgetManagerDelegate delegate) { return null; } + protected native void queueRunnable(Runnable aRunnable); protected native boolean platformExit(); } diff --git a/app/src/main/res/layout/visionglass_layout.xml b/app/src/main/res/layout/visionglass_layout.xml index 3ef7aa954a..9779d62045 100644 --- a/app/src/main/res/layout/visionglass_layout.xml +++ b/app/src/main/res/layout/visionglass_layout.xml @@ -1,5 +1,6 @@ @@ -28,14 +29,13 @@ android:textOn="" /> + android:src="@drawable/ic_icon_microphone" /> + + + + + + + + + + + + + + + + + + + { + // We don't really need the coordinates of the click because we use the position + // of the aim in the 3D environment. + queueRunnable(() -> touchEvent(false, 0, 0)); + }); + + touchpad.setOnTouchListener((view, event) -> { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_MOVE: + // We don't really need the coordinates of the click because we use the position + // of the aim in the 3D environment. + queueRunnable(() -> touchEvent(true, 0, 0)); + break; + case MotionEvent.ACTION_UP: + // We'd emit the touchEvent in the onClick listener of the view. This way both + // user and system activated clicks (e.g. a11y) will work. + view.performClick(); + break; + default: + return false; + } + return true; + }); + + ImageButton backButton = findViewById(R.id.back_button); + backButton.setOnClickListener(v -> onBackPressed()); + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + private void initVisionGlass() { Log.d(LOGTAG, "initVisionGlass"); @@ -203,54 +249,6 @@ public void onError(String s, int i) { } }); - setContentView(R.layout.visionglass_layout); - - View touchpad = findViewById(R.id.touchpad); - touchpad.setOnClickListener(v -> { - // We don't really need the coordinates of the click because we use the position - // of the aim in the 3D environment. - queueRunnable(() -> touchEvent(false, 0, 0)); - }); - - touchpad.setOnTouchListener((view, event) -> { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_MOVE: - // We don't really need the coordinates of the click because we use the position - // of the aim in the 3D environment. - queueRunnable(() -> touchEvent(true, 0, 0)); - break; - case MotionEvent.ACTION_UP: - // We'd emit the touchEvent in the onClick listener of the view. This way both - // user and system activated clicks (e.g. a11y) will work. - view.performClick(); - break; - default: - return false; - } - return true; - }); - - ImageButton backButton = findViewById(R.id.back_button); - ImageButton homeButton = findViewById(R.id.home_button); - - backButton.setOnClickListener(v -> onBackPressed()); - homeButton.setOnClickListener(v -> runPhoneUICallback((activity) -> { - Windows windows = activity.getWindows(); - if (windows == null) { - Log.e(LOGTAG, "Cannot load homepage because Windows object is null"); - return; - } - windows.getFocusedWindow().loadHome(); - })); - - ToggleButton headlockButton = findViewById(R.id.headlock_toggle_button); - headlockButton.setOnClickListener(v -> runPhoneUICallback((activity) -> { - activity.setHeadLockEnabled(headlockButton.isChecked()); - })); - - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - // Show the app if (getLifecycle().getCurrentState() == Lifecycle.State.RESUMED) { updateDisplays(); @@ -419,6 +417,133 @@ private void updateDisplays() { runOnUiThread(this::showPresentation); } + public final PlatformActivityPlugin createPlatformPlugin(WidgetManagerDelegate delegate) { + return new PlatformActivityPluginVisionGlass(delegate); + } + + private class PlatformActivityPluginVisionGlass implements PlatformActivityPlugin { + private WidgetManagerDelegate mDelegate; + private WMediaSession.Delegate mMediaSessionDelegate; + private SeekBar mMediaSeekbar; + + PlatformActivityPluginVisionGlass(WidgetManagerDelegate delegate) { + mDelegate = delegate; + setupPhoneUI(); + } + + @Override + public void onKeyboardVisibilityChange(boolean isVisible) { + mVoiceSearchButton.setEnabled(isVisible); + } + + @Override + public void onVideoAvailabilityChange() { + boolean isAvailable = mDelegate.getWindows().isVideoAvailable(); + findViewById(R.id.phoneUIMediaControls).setVisibility(isAvailable ? View.VISIBLE : View.GONE); + + Media media = getActiveMedia(); + if (!isAvailable) { + if (media != null) { + media.removeMediaListener(mMediaSessionDelegate); + mMediaSessionDelegate = null; + } + return; + } + + assert(media != null); + media.addMediaListener(new WMediaSession.Delegate() { + @Override + public void onPlay(@NonNull WSession session, @NonNull WMediaSession mediaSession) { + ((ImageButton) findViewById(R.id.phoneUIPlayButton)).setImageResource(R.drawable.ic_icon_media_pause); + } + + @Override + public void onPause(@NonNull WSession session, @NonNull WMediaSession mediaSession) { + ((ImageButton) findViewById(R.id.phoneUIPlayButton)).setImageResource(R.drawable.ic_icon_media_play); + } + + @Override + public void onPositionState(@NonNull WSession session, @NonNull WMediaSession mediaSession, @NonNull WMediaSession.PositionState state) { + mMediaSeekbar.setProgress((int) ((state.position / state.duration) * 100), false); + } + }); + } + + private Media getActiveMedia() { + assert mDelegate.getWindows() != null; + assert mDelegate.getWindows().getFocusedWindow() != null; + return mDelegate.getWindows().getFocusedWindow().getSession().getActiveVideo(); + } + + // Setup the phone UI callbacks that require access to the WindowManagerDelegate. + private void setupPhoneUI() { + findViewById(R.id.home_button).setOnClickListener(v -> { + mDelegate.getWindows().getFocusedWindow().loadHome(); + }); + + ToggleButton headlockButton = findViewById(R.id.headlock_toggle_button); + headlockButton.setOnClickListener(v -> { + mDelegate.setHeadLockEnabled(headlockButton.isChecked()); + }); + + findViewById(R.id.phoneUIPlayButton).setOnClickListener(v -> { + Media media = getActiveMedia(); + if (media == null) + return; + if (media.isPlaying()) { + media.pause(); + } else { + media.play(); + } + }); + + findViewById(R.id.phoneUISeekBackward10Button).setOnClickListener(v -> { + Media media = getActiveMedia(); + if (media == null) + return; + media.seek(media.getCurrentTime() - 10); + }); + + findViewById(R.id.phoneUISeekForward30Button).setOnClickListener(v -> { + Media media = getActiveMedia(); + if (media == null) + return; + media.seek(media.getCurrentTime() + 30); + }); + + findViewById(R.id.phoneUIVoiceButton).setOnClickListener(v -> { + // Delegate all the voice input handling in the KeyboardWidget which already handles + // all the potential voice input cases. + mDelegate.getKeyboard().simulateVoiceButtonClick(); + }); + + findViewById(R.id.phoneUIMuteButton).setOnClickListener(v -> { + Media media = getActiveMedia(); + if (media == null) + return; + media.setMuted(!media.isMuted()); + ((ImageButton) findViewById(R.id.phoneUIMuteButton)).setImageResource(!media.isMuted() ? R.drawable.ic_icon_media_volume : R.drawable.ic_icon_media_volume_muted); + }); + + mMediaSeekbar = findViewById(R.id.phoneUIMediaSeekBar); + mMediaSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) {} + + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + Media media = getActiveMedia(); + if (media == null || !media.canSeek() || media.getDuration() == 0) + return; + media.seek((seekBar.getProgress() / 100.0) * media.getDuration()); + } + }); + } + } + private final DisplayManager.DisplayListener mDisplayListener = new DisplayManager.DisplayListener() { @Override diff --git a/app/src/wavevr/java/org/mozilla/vrbrowser/PlatformActivity.java b/app/src/wavevr/java/org/mozilla/vrbrowser/PlatformActivity.java index 1b64bea09f..d4509d5300 100644 --- a/app/src/wavevr/java/org/mozilla/vrbrowser/PlatformActivity.java +++ b/app/src/wavevr/java/org/mozilla/vrbrowser/PlatformActivity.java @@ -13,6 +13,7 @@ import android.os.Bundle; import android.view.KeyEvent; +import com.igalia.wolvic.ui.widgets.WidgetManagerDelegate; import com.igalia.wolvic.utils.SystemUtils; public class PlatformActivity extends VRActivity { @@ -60,6 +61,8 @@ public void onBackPressed() { // the system menu to exit applications. } + public final PlatformActivityPlugin createPlatformPlugin(WidgetManagerDelegate delegate) { return null; } + protected native void queueRunnable(Runnable aRunnable); protected native void initializeJava(AssetManager aAssets); }