fix: close stale Quest XR sessions before reentry

This commit is contained in:
XxJSONDeruloxX committed 2026-10-15 15:31:59 -04:00
1 parent 65c6ca3552
commit 19a8beea52
2 files changed
+62 -5

No files matched your search

@@ -2,9 +2,12 @@ package com.droiddeck.launcher
import android.app.NativeActivity
import android.os.Bundle
import android.os.SystemClock
import android.util.Log
import android.widget.Toast
import com.droiddeck.launcher.gpu.SteamVr
import com.droiddeck.launcher.session.SessionService
import com.droiddeck.launcher.session.SessionState
import com.droiddeck.launcher.wayland.CompositorHost
import com.droiddeck.launcher.wayland.WaylandCompositor
@@ -12,7 +15,9 @@ class QuestXrActivity : NativeActivity() {
private var steamVrStarted = false
@Volatile private var closing = false
@Volatile private var steamVrStatus = SteamVr.Status.UNAVAILABLE
private var sessionId: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
sessionId = SessionState.sessionId
SessionService.setXrActivityVisible(this, true)
super.onCreate(savedInstanceState)
}
@@ -36,16 +41,59 @@ class QuestXrActivity : NativeActivity() {
if (!steamVrStarted) {
steamVrStarted = true
Thread({
if (!SteamVr.run(this, "start")) Log.e("DroidDeckXR", "SteamVR start failed")
while (!closing) {
steamVrStatus = SteamVr.status(this)
Thread.sleep(1000)
try {
val started = synchronized(steamVrLock) {
if (closing || !sessionAlive()) false else {
steamVrOwner = this
SteamVr.run(this, "start")
}
}
if (!started) {
leaveXr("SteamVR start failed", true)
return@Thread
}
val startupDeadline = SystemClock.elapsedRealtime() + 30_000
var lastRunning: Long? = null
while (!closing) {
if (!sessionAlive()) {
leaveXr("Steam session ended", false)
break
}
steamVrStatus = SteamVr.status(this)
val now = SystemClock.elapsedRealtime()
if (steamVrStatus == SteamVr.Status.RUNNING) lastRunning = now
val deadline = lastRunning?.plus(5_000) ?: startupDeadline
if (steamVrStatus != SteamVr.Status.RUNNING && now >= deadline) {
leaveXr("SteamVR stopped or did not finish starting", true)
break
}
Thread.sleep(1000)
}
} finally {
synchronized(steamVrLock) {
// A retiring activity must not stop a replacement's SteamVR session.
if (steamVrOwner === this) {
if (sessionAlive()) SteamVr.run(this, "stop")
steamVrOwner = null
}
}
}
SteamVr.run(this, "stop")
}, "quest-steamvr-start").start()
}
}
private fun sessionAlive() = SessionState.running && !SessionState.stopRequested &&
sessionId != null && SessionState.sessionId == sessionId
private fun leaveXr(reason: String, failed: Boolean) {
Log.w("DroidDeckXR", reason)
closing = true
runOnUiThread {
if (failed) Toast.makeText(this, R.string.cardboard_steamvr_failed, Toast.LENGTH_LONG).show()
finish()
}
}
fun onXrFrame(frameTimeNanos: Long) {
WaylandCompositor.nativeVsync(frameTimeNanos)
}
@@ -62,4 +110,9 @@ class QuestXrActivity : NativeActivity() {
SessionService.setXrActivityVisible(this, false)
super.onStop()
}
companion object {
private val steamVrLock = Any()
private var steamVrOwner: QuestXrActivity? = null
}
}
+4
View File
@@ -81,6 +81,10 @@ The panel compositor detaches before OpenXR starts displaying guest frames.
Session suspension accounts for the immersive activity while the panel is hidden.
Closing the immersive activity stops SteamVR. The panel remains available for
returning to the normal Steam session.
The immersive activity belongs to the Steam session that opened it. Ending that
session closes XR; a failed startup or stopped SteamVR also returns to the panel
with an error instead of leaving a black scene. Enter SteamVR can then start a
fresh connection.
With Steam already running, `python3 tools/test_quest_xr.py --serial SERIAL`
launches the immersive activity and verifies advancing owned images, tracked