Android: add optional on-screen button to open the stream menu #819

Open
MicheleLoiDWR wants to merge 1 commit from MicheleLoiDWR/android-stream-menu-button into main
MicheleLoiDWR commented 2026-09-29 04:51:08 -05:00 (Migrated from github.com)

Summary

Adds an optional button at the top of the stream that opens the stream menu (the bar with the on-screen controls switches and display-mode buttons). Off by default.

On the device I tested on (gesture navigation), that menu can't be reopened once it hides. It's shown from the window-insets listener when the system bars appear, but StreamActivity hides the bars with BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE. As far as I can tell, in that mode an edge swipe only shows the bars temporarily on top of the app and the app is never notified, so the listener never fires. (Pulling the notification shade down does notify it, which is why the menu sometimes flashes for a split second.) I'd expect other gesture-navigation devices to behave the same, but I've only been able to verify it on this one.

What users get

Settings → General:

Setting Default
Menu Button on Screen Off A small ⋮ button at the top-center of the stream that opens/closes the stream menu
Menu Button Opacity 50% 10–100%, disabled while the button is off

The button sits at the top-center because that's the one spot the full on-screen controls layout leaves free (its touchpad starts 32dp from the top). The menu hides itself after the existing 2-second timeout, or when the button is tapped again.

02-settings-general

Why a button instead of fixing the swipe

I tried restoring swipe-to-open first. The pre-edge-to-edge code used SYSTEM_UI_FLAG_IMMERSIVE, whose new-API equivalent is BEHAVIOR_DEFAULT. In that mode the app is notified. But with gesture navigation, the same swipe up also starts Android's home gesture, so the app is pulled out of focus while the menu is showing, and the menu closes as soon as focus returns. That makes every accidental edge swipe mid-game worse, so I kept transient bars and added an explicit trigger.

While investigating I also noticed that the listener checks insets.isVisible(Type.systemBars()), and in my testing that never became true, even when the status and navigation bars were both reported visible. I suspect it's because systemBars() also includes the caption bar, which a full-screen app doesn't have. It doesn't matter with transient bars (the listener isn't called for swipes anyway), so I left it untouched here.

Implementation

  • StreamActivity: shows and wires the button when enabled (toggleOverlay()). The auto-hide timer now also hides the overlay directly: when it's opened from the button, the system bars never appear, so hiding them wouldn't trigger the insets listener. With the menu opened the existing way, this changes nothing: the overlay hid at the same moment before.
  • activity_stream.xml: the button (32dp, dark circle, ic_overflow), plus a new bg_stream_menu_button.xml background.
  • Preferences / preferences.xml / strings.xml: the two settings. SettingsFragment's DataStore gets the new keys, including new getInt/putInt overrides for the slider.

Testing

  • Tested on an AYN Thor (Android 13, gesture navigation) streaming from a PS5: button shown/hidden by the setting, opacity applied, the menu toggles and auto-hides, and the switches inside it work.
  • Existing unit tests pass. ./gradlew lintDebug: no new warnings on the changed files.

Notes

Developed with the assistance of Claude Code, and tested on real hardware as described above.

## Summary Adds an optional button at the top of the stream that opens the stream menu (the bar with the on-screen controls switches and display-mode buttons). Off by default. On the device I tested on (gesture navigation), that menu can't be reopened once it hides. It's shown from the window-insets listener when the system bars appear, but `StreamActivity` hides the bars with `BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE`. As far as I can tell, in that mode an edge swipe only shows the bars temporarily on top of the app and the app is never notified, so the listener never fires. (Pulling the notification shade down does notify it, which is why the menu sometimes flashes for a split second.) I'd expect other gesture-navigation devices to behave the same, but I've only been able to verify it on this one. ## What users get **Settings → General:** | Setting | Default | | |---|---|---| | Menu Button on Screen | Off | A small ⋮ button at the top-center of the stream that opens/closes the stream menu | | Menu Button Opacity | 50% | 10–100%, disabled while the button is off | The button sits at the top-center because that's the one spot the full on-screen controls layout leaves free (its touchpad starts 32dp from the top). The menu hides itself after the existing 2-second timeout, or when the button is tapped again. <img width="1920" height="1080" alt="02-settings-general" src="https://github.com/user-attachments/assets/95146432-5949-4a24-a0df-b27d9f2cc4d7" /> ## Why a button instead of fixing the swipe I tried restoring swipe-to-open first. The pre-edge-to-edge code used `SYSTEM_UI_FLAG_IMMERSIVE`, whose new-API equivalent is `BEHAVIOR_DEFAULT`. In that mode the app *is* notified. But with gesture navigation, the same swipe up also starts Android's home gesture, so the app is pulled out of focus while the menu is showing, and the menu closes as soon as focus returns. That makes every accidental edge swipe mid-game worse, so I kept transient bars and added an explicit trigger. While investigating I also noticed that the listener checks `insets.isVisible(Type.systemBars())`, and in my testing that never became true, even when the status and navigation bars were both reported visible. I suspect it's because `systemBars()` also includes the caption bar, which a full-screen app doesn't have. It doesn't matter with transient bars (the listener isn't called for swipes anyway), so I left it untouched here. ## Implementation - `StreamActivity`: shows and wires the button when enabled (`toggleOverlay()`). The auto-hide timer now also hides the overlay directly: when it's opened from the button, the system bars never appear, so hiding them wouldn't trigger the insets listener. With the menu opened the existing way, this changes nothing: the overlay hid at the same moment before. - `activity_stream.xml`: the button (32dp, dark circle, `ic_overflow`), plus a new `bg_stream_menu_button.xml` background. - `Preferences` / `preferences.xml` / `strings.xml`: the two settings. `SettingsFragment`'s `DataStore` gets the new keys, including new `getInt`/`putInt` overrides for the slider. ## Testing - Tested on an AYN Thor (Android 13, gesture navigation) streaming from a PS5: button shown/hidden by the setting, opacity applied, the menu toggles and auto-hides, and the switches inside it work. - Existing unit tests pass. `./gradlew lintDebug`: no new warnings on the changed files. ## Notes Developed with the assistance of Claude Code, and tested on real hardware as described above.
This pull request is broken due to missing fork information.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin 'MicheleLoiDWR/android-stream-menu-button:MicheleLoiDWR/android-stream-menu-button'
git switch 'MicheleLoiDWR/android-stream-menu-button'

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch 'main'
git merge --no-ff 'MicheleLoiDWR/android-stream-menu-button'
git switch 'MicheleLoiDWR/android-stream-menu-button'
git rebase 'main'
git switch 'main'
git merge --ff-only 'MicheleLoiDWR/android-stream-menu-button'
git switch 'MicheleLoiDWR/android-stream-menu-button'
git rebase 'main'
git switch 'main'
git merge --no-ff 'MicheleLoiDWR/android-stream-menu-button'
git switch 'main'
git merge --squash 'MicheleLoiDWR/android-stream-menu-button'
git switch 'main'
git merge --ff-only 'MicheleLoiDWR/android-stream-menu-button'
git switch 'main'
git merge 'MicheleLoiDWR/android-stream-menu-button'
git push origin 'main'
Sign in to join this conversation.
No description provided.