diff --git a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt index 8deb14120..a162ab14b 100644 --- a/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/SessionActivity.kt @@ -121,6 +121,7 @@ import com.droiddeck.launcher.session.SessionArtifacts import com.droiddeck.launcher.session.SessionLogShare import com.droiddeck.launcher.session.SessionPhase import com.droiddeck.launcher.session.SessionPaths +import com.droiddeck.launcher.session.SessionLoadingDecision import com.droiddeck.launcher.wayland.HdrSupport import com.droiddeck.launcher.session.SessionService import com.droiddeck.launcher.session.SessionState @@ -2435,6 +2436,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { private fun watchSession() { val handler = Handler(Looper.getMainLooper()) var ticks = 0 + val loadingStartedAt = SystemClock.elapsedRealtime() val poll = object : Runnable { override fun run() { if (!watching) return @@ -2447,6 +2449,12 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { finish() return } + if (loading.visible && !loading.ended && !SessionState.running && !installingRuntime && + SessionLoadingDecision.shouldReturnToLauncher( + loadingStartedAt, SystemClock.elapsedRealtime(), SessionState.running, SessionState.phase)) { + finish() + return + } if (loading.visible && !loading.ended) { if (!installingRuntime) loading.update(this@SessionActivity, SessionState.logFile) if (ticks++ % 2 == 0) loading.tick() @@ -2471,8 +2479,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback { val hint = sessionExitHint(status) runOnUiThread { if (isFinishing || isDestroyed) return@runOnUiThread - if (status == 0) SessionState.relaunch?.let { next -> - SessionState.relaunch = null + if (status == 0) SessionState.takeRelaunch()?.let { next -> next.putExtra(EXTRA_RETURN_HOME, intent.getBooleanExtra(EXTRA_RETURN_HOME, false)) next.putExtra(EXTRA_LINK_TASK, intent.getIntExtra(EXTRA_LINK_TASK, -1)) setIntent(next) diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionEvents.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionEvents.kt index 26b63fd5c..6c0a07645 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionEvents.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionEvents.kt @@ -14,6 +14,7 @@ object SessionEvents { SessionPaths.take() val dir = SessionPaths.beginOrCurrent(context, label ?: SessionPaths.label(mode, null)) synchronized(SessionState) { + SessionState.clearRelaunch() SessionState.phase = SessionPhase.PREPARING SessionState.mode = mode SessionState.sessionId = dir.name diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionLoadingDecision.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionLoadingDecision.kt new file mode 100644 index 000000000..1c5d034df --- /dev/null +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionLoadingDecision.kt @@ -0,0 +1,8 @@ +package com.droiddeck.launcher.session + +internal object SessionLoadingDecision { + const val TIMEOUT_MS = 10_000L + + fun shouldReturnToLauncher(startedAt: Long, now: Long, running: Boolean, phase: SessionPhase): Boolean = + !running && phase in setOf(SessionPhase.IDLE, SessionPhase.FAILED) && now - startedAt >= TIMEOUT_MS +} diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt index cc67bc800..5fb5e37f2 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt @@ -1697,8 +1697,8 @@ class SessionService : Service() { .putExtra(EXTRA_STEAM_UI, ui) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) if (url.isNotEmpty()) next.putExtra(EXTRA_STEAM_URL, url) - SessionState.relaunch = next - android.os.Handler(android.os.Looper.getMainLooper()).post { stopSession(0) } + SessionState.queueRelaunch(next) + android.os.Handler(android.os.Looper.getMainLooper()).post { stopSession(0, relaunch = true) } } } watcher.startWatching() @@ -2013,7 +2013,7 @@ class SessionService : Service() { } } - private fun stopSession(status: Int, gen: Int? = null) { + private fun stopSession(status: Int, gen: Int? = null, relaunch: Boolean = false) { if (gen != null && gen != sessionGen) { Log.i(TAG, "ignoring a stop from session $gen; session $sessionGen is current") return @@ -2024,6 +2024,7 @@ class SessionService : Service() { cancelQueuedSteamGame() gameExitWatchToken++ if (!SessionState.running) return + if (relaunch) SessionState.confirmRelaunchStop() SessionEvents.record("guest.exited", mapOf("status" to status)) SessionEvents.transition(SessionPhase.STOPPING, "session.stopping", mapOf("status" to status)) SessionState.running = false diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionState.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionState.kt index fa6eb38c9..5f17212e5 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionState.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionState.kt @@ -73,7 +73,44 @@ object SessionState { @Volatile var steamClientArch: String? = null var steamVr by mutableStateOf(false) + @Volatile var relaunch: android.content.Intent? = null + @Volatile + private var relaunchSessionId: String? = null + @Volatile + private var relaunchStopRequested = false + + fun queueRelaunch(intent: android.content.Intent) { + synchronized(this) { + relaunch = intent + relaunchSessionId = sessionId + relaunchStopRequested = false + } + } + + fun confirmRelaunchStop() { + synchronized(this) { + relaunchStopRequested = relaunch != null && relaunchSessionId == sessionId + } + } + + fun takeRelaunch(): android.content.Intent? = synchronized(this) { + val next = relaunch + val valid = relaunchStopRequested && relaunchSessionId == sessionId + relaunch = null + relaunchSessionId = null + relaunchStopRequested = false + next.takeIf { valid } + } + + fun clearRelaunch() { + synchronized(this) { + relaunch = null + relaunchSessionId = null + relaunchStopRequested = false + } + } + @JvmStatic var hdr = false @Volatile private var deckPadValue = false diff --git a/app/src/test/java/com/droiddeck/launcher/session/SessionLoadingDecisionTest.kt b/app/src/test/java/com/droiddeck/launcher/session/SessionLoadingDecisionTest.kt new file mode 100644 index 000000000..bdfd2fb5f --- /dev/null +++ b/app/src/test/java/com/droiddeck/launcher/session/SessionLoadingDecisionTest.kt @@ -0,0 +1,53 @@ +package com.droiddeck.launcher.session + +import android.content.Intent +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Before +import org.junit.Test + +class SessionLoadingDecisionTest { + private var previousSessionId: String? = null + private var previousRelaunch: Intent? = null + + @Before + fun setUp() { + previousSessionId = SessionState.sessionId + previousRelaunch = SessionState.relaunch + SessionState.clearRelaunch() + } + + @After + fun tearDown() { + SessionState.sessionId = previousSessionId + SessionState.relaunch = previousRelaunch + SessionState.clearRelaunch() + if (previousRelaunch != null) SessionState.queueRelaunch(previousRelaunch!!) + } + + @Test + fun loadingTimesOutOnlyWhenNoSessionStarted() { + assertEquals(false, SessionLoadingDecision.shouldReturnToLauncher(100L, 10_099L, false, SessionPhase.IDLE)) + assertEquals(true, SessionLoadingDecision.shouldReturnToLauncher(100L, 10_100L, false, SessionPhase.IDLE)) + assertEquals(false, SessionLoadingDecision.shouldReturnToLauncher(100L, 10_100L, true, SessionPhase.IDLE)) + assertEquals(false, SessionLoadingDecision.shouldReturnToLauncher(100L, 10_100L, false, SessionPhase.PREPARING)) + } + + @Test + fun relaunchRequiresTheCurrentSessionToHaveRequestedTheStop() { + SessionState.sessionId = "session-a" + val next = Intent().apply { action = "next" } + SessionState.queueRelaunch(next) + assertNull(SessionState.takeRelaunch()) + SessionState.queueRelaunch(next) + SessionState.confirmRelaunchStop() + assertEquals(next, SessionState.takeRelaunch()) + assertNull(SessionState.takeRelaunch()) + + SessionState.queueRelaunch(next) + SessionState.confirmRelaunchStop() + SessionState.sessionId = "session-b" + assertNull(SessionState.takeRelaunch()) + } +}