Skip to content

fix(nova): route dual-screen controls to companion display#117

Draft
papi-ux wants to merge 11 commits into
masterfrom
feature/thor-dual-screen-companion
Draft

fix(nova): route dual-screen controls to companion display#117
papi-ux wants to merge 11 commits into
masterfrom
feature/thor-dual-screen-companion

Conversation

@papi-ux

@papi-ux papi-ux commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Refs #115. Builds on the display-target selector from #98/#106.

  • Derives a separate companion/control display from the actual Android stream display.
  • Routes ExternalDisplayControlActivity to the non-stream companion display instead of hard-coding Display.DEFAULT_DISPLAY.
  • Starts companion controls even when the stream is on the primary/default display, which is the Thor Largest / This device screen path.
  • Splits stream bootstrapping into a dedicated GameDisplayLaunchTrampolineActivity so the singleton companion-control activity no longer doubles as a launch trampoline.
  • Forces stream launch/relaunch onto the requested Android display, including Display.DEFAULT_DISPLAY, even when Nova was opened from the Thor bottom/companion screen.
  • Differentiates stream display removal from companion display removal and unregisters the display listener.
  • Adds safe display-role logs for field testing:
    • Nova: Android display role stream id=... target=...
    • Nova: Android display role companion id=... stream_id=...
    • Nova: Android display launch stream id=...

Field-test gate before merge

This PR should stay draft until real AYN Thor evidence confirms:

  1. Enable Use Android external display.
  2. Set Android stream display to Largest display or This device screen.
  3. Launch Nova from the top screen, then launch a stream.
  4. Confirm the game stays on the top/main screen.
  5. Confirm the bottom screen shows Nova's touchpad/keyboard/menu controls.
  6. Quit/disconnect cleanly.
  7. Launch Nova from the bottom screen, then launch a stream.
  8. Confirm the game still moves to the selected top/main stream display instead of leaving the top untouched.
  9. Confirm the bottom screen shows Nova controls instead of a black placeholder.
  10. Disconnect/relaunch once and confirm it does not hang at “preparing video, audio and controller input...”.
  11. Open keyboard, full keyboard, menu, and zoom/pan once.
  12. Attach or summarize a fresh Nova log if anything lands on the wrong screen.

Expected safe log shape:

Nova: Android display role stream id=0 target=largest
Nova: Android display launch stream id=0
Nova: Android display role companion id=4 stream_id=0

IDs may differ by firmware/device.

Test plan

Executed locally from an isolated worktree after initializing native submodules:

  • RED guard: NovaExternalDisplayRoutingSourceGuardTest failed before the dedicated trampoline split because GameDisplayLaunchTrampolineActivity did not exist, ServerHelper still targeted ExternalDisplayControlActivity.EXTRA_LAUNCH_INTENT, ExternalDisplayControlActivity still bootstrapped Game, and Game.relaunchStream() still used plain app-context startActivity.
  • ANDROID_HOME=/home/papi/Android/Sdk ANDROID_SDK_ROOT=/home/papi/Android/Sdk ./gradlew :app:testDebugUnitTest --tests com.papi.nova.utils.NovaExternalDisplayRoutingSourceGuardTest --tests com.papi.nova.utils.KotlinExternalDisplayUiMigrationTest --tests com.papi.nova.utils.AndroidStreamDisplayTargetTest --console=plain
  • ANDROID_HOME=/home/papi/Android/Sdk ANDROID_SDK_ROOT=/home/papi/Android/Sdk ./gradlew :app:compileRootDebugKotlin :app:lintRootDebug :app:testDebugUnitTest :app:assembleDebug --console=plain
  • After switching the trampoline from AppCompatActivity to plain Activity for the platform translucent theme: ANDROID_HOME=/home/papi/Android/Sdk ANDROID_SDK_ROOT=/home/papi/Android/Sdk ./gradlew :app:testDebugUnitTest --tests com.papi.nova.utils.NovaExternalDisplayRoutingSourceGuardTest --tests com.papi.nova.utils.KotlinExternalDisplayUiMigrationTest --tests com.papi.nova.utils.AndroidStreamDisplayTargetTest :app:compileRootDebugKotlin :app:lintRootDebug :app:assembleDebug --console=plain
  • git diff --check
  • Static added-line scan for secrets/shell/eval/pickle/SQL patterns: no findings.
  • Lint XML changed-line scan: no findings on changed lines.

Debug APKs were produced locally for root/non-root debug flavors and arm64-v8a/armeabi-v7a/x86_64 ABIs.

Field-test APK

Prerelease asset:
https://github.com/papi-ux/nova/releases/tag/pr117-thor-dual-screen-debug

Current arm64-v8a debug APK SHA256:
05bbb02598c5a4c97eb4e8c6d4156f243ac9e448afda2e18370393151116e53b

Notes

  • This is Lane A only: functional dual-display routing and lifecycle cleanup.
  • It intentionally does not include the later companion dashboard / NovaHUD polish lane.
  • No Polaris host/protocol changes are included.
  • Logging is limited to display role/id/target/stream_id; no host credentials, certs, tokens, IPs, URLs, or computer names are logged by the new role lines.
  • If audio still routes to the bottom/control display after this build, treat that as a separate Android audio-output routing follow-up with a fresh log and exact device-output behavior.

@papi-ux

papi-ux commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

Thor field-test APK for this PR

I published a debug-signed arm64 field-test APK for PR #117 here:

This APK is debug-signed for testing only:

C=US, O=Android, CN=Android Debug

If Android reports a signature mismatch, uninstall the existing release build first, then install this APK:

adb uninstall com.papi.nova
adb install Nova-PR117-Thor-arm64-v8a-debug.apk

Thor test checklist

  1. Install the APK on the Thor.
  2. Open Nova and connect/pair normally.
  3. Enable Use Android external display.
  4. Set Android stream display to Largest display or This device screen.
  5. Launch a stream.
  6. Confirm the game stays on the top/main screen.
  7. Confirm the bottom screen shows Nova's touchpad/keyboard/menu controls.
  8. Exercise once:
    • touchpad movement/click
    • Android soft keyboard
    • full keyboard
    • game menu / quick shortcuts
    • zoom/pan toggle
  9. Quit the stream.
  10. Confirm the bottom controls close cleanly, no stuck notification remains, and relaunch works.

Logs to capture if anything lands wrong

Please attach/summarize fresh Nova logs containing these lines:

Nova: Android display role stream id=... target=...
Nova: Android display role companion id=... stream_id=...

Expected Thor-ish shape for Largest display:

Nova: Android display role stream id=0 target=largest
Nova: Android display role companion id=4 stream_id=0

Display IDs may differ by firmware/device.

This PR should remain draft until real Thor hardware confirms the routing behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant