Skip to content

Fix cold-launch intent race, add failure feedback#884

Merged
utkarshdalal merged 2 commits intoutkarshdalal:masterfrom
jeremybernstein:jb/fix-intent-launch
Mar 18, 2026
Merged

Fix cold-launch intent race, add failure feedback#884
utkarshdalal merged 2 commits intoutkarshdalal:masterfrom
jeremybernstein:jb/fix-intent-launch

Conversation

@jeremybernstein
Copy link
Contributor

@jeremybernstein jeremybernstein commented Mar 16, 2026

Closes #883

Summary

  • Fix cold-start intent: split into warm (onNewIntent → emit event) vs cold (store pending, PluviaMain consumes when UI ready)
  • Snackbar on unparseable LAUNCH_GAME intent
  • Dialog when intent targets uninstalled game
  • Snackbar when Steam login required for cold-start intent
  • Remove trivial resolveNotInstalledGameName wrapper

Test plan

  • Cold launch via adb shell am start -a app.gamenative.LAUNCH_GAME --es app_id <id> — game launches after login
  • Warm launch via same intent while app open — launches immediately
  • Intent with invalid app_id → snackbar error
  • Intent for uninstalled game → "not installed" dialog
  • Steam game cold launch while logged out → snackbar, launches after login

Summary by cubic

Fixes dropped LAUNCH_GAME intents on cold start by queuing them until the UI is ready; warm launches still fire immediately. Adds clear, localized feedback when an intent is invalid, the game isn’t installed, or Steam login is required.

  • Bug Fixes

    • Split intent handling: warm launches emit on onNewIntent; all cold launches are stored and consumed by PluviaMain when the UI is ready.
    • Prevented cold-start launches (including non‑Steam) from failing silently due to a non-replaying event bus.
    • Ignored intents re-delivered from Recents to avoid duplicate launches.
  • New Features

    • Snackbar when a LAUNCH_GAME intent has invalid extras.
    • Dialog when the target game is not installed (shows the game name).
    • Snackbar when Steam login is required; the intent is queued and retried after login.

Written for commit 3e44cf8. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Improved external game-launch handling with pending-request support at startup.
    • Detects Steam login requirements and defers launches until login when needed.
    • Shows user-facing notifications for invalid or failed launch shortcuts.
  • Localization

    • Added translations for launch-failure and Steam-login-required messages across multiple languages.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 16 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/src/main/java/app/gamenative/MainActivity.kt">

<violation number="1" location="app/src/main/java/app/gamenative/MainActivity.kt:230">
P2: Warm-intent launch path no longer clears stale pending launch request, allowing an older queued launch to execute later.</violation>
</file>

<file name="app/src/main/java/app/gamenative/ui/PluviaMain.kt">

<violation number="1" location="app/src/main/java/app/gamenative/ui/PluviaMain.kt:273">
P1: Warm external Steam launches bypass the new login-required gate, allowing logged-out Steam launch flow to proceed and hit logged-in assumptions.</violation>

<violation number="2" location="app/src/main/java/app/gamenative/ui/PluviaMain.kt:287">
P2: Cold-start intent launch applies temporary container override but calls `preLaunchApp` without `useTemporaryOverride`, so override can be ignored.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 264b812e-b1b4-4382-b157-fe6f1de867cc

📥 Commits

Reviewing files that changed from the base of the PR and between 79f929f and 3e44cf8.

📒 Files selected for processing (16)
  • app/src/main/java/app/gamenative/MainActivity.kt
  • app/src/main/java/app/gamenative/ui/PluviaMain.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (9)
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-da/strings.xml

📝 Walkthrough

Walkthrough

MainActivity now distinguishes cold-start vs new-intent launches (adds isNewIntent) and stores or consumes pending external launch requests accordingly. PluviaMain adds Steam-login gating and processes pending launches at cold start. New localized strings for launch failures and Steam-login requirements added across multiple locales.

Changes

Cohort / File(s) Summary
Intent launch handling
app/src/main/java/app/gamenative/MainActivity.kt, app/src/main/java/app/gamenative/ui/PluviaMain.kt
Added isNewIntent to launch handling; MainActivity queues pending launches on cold start and consumes/emit on new-intent. Reworked external-launch flow to persist or emit AndroidEvent.ExternalGameLaunch, handle parse failures with a Snackbar, and propagate/apply temporary container overrides. PluviaMain adds needsSteamLogin gating and processes pending launches in a LaunchedEffect.
User feedback / Snackbar
app/src/main/java/app/gamenative/MainActivity.kt
Imported and used SnackbarManager to surface intent parse failures and Steam-login-required messages.
Localization strings
app/src/main/res/values*/strings.xml
app/src/main/res/values/strings.xml, .../values-da/..., .../values-de/..., .../values-es/..., .../values-fr/..., .../values-it/..., .../values-ko/..., .../values-pl/..., .../values-pt-rBR/..., .../values-ro/..., .../values-ru/..., .../values-uk/..., .../values-zh-rCN/..., .../values-zh-rTW/...
Added two new string resources across locales: intent_launch_failed and intent_launch_steam_login_required.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant App as App (Cold Start)
    participant MA as MainActivity
    participant IL as IntentLaunchManager
    participant PM as PluviaMain
    participant UI as Snackbar / Dialog

    App->>MA: deliver external Intent (onCreate)
    MA->>MA: handleLaunchIntent(intent, isNewIntent=false)
    alt parse success
        MA->>IL: store pending launch
    else parse fails / LAUNCH_GAME null
        MA->>UI: show `intent_launch_failed` via SnackbarManager
    end

    PM->>PM: LaunchedEffect on composition
    PM->>IL: check pending launch
    alt pending exists
        PM->>PM: needsSteamLogin? (container lookup / login state)
        alt steam login required
            PM->>UI: show `intent_launch_steam_login_required` Snackbar and persist pending
        else
            PM->>PM: resolve appId, apply temporary override if present
            PM->>PM: trigger preLaunchApp (mark external launch)
        end
    end

    User->>MA: onNewIntent (return / explicit new intent)
    MA->>MA: handleLaunchIntent(intent, isNewIntent=true)
    MA->>IL: consume pending launch
    MA->>PM: emit AndroidEvent.ExternalGameLaunch (with override if any)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 I hopped through intents both new and old,
Pending launches tucked in my fold,
I ask Steam politely before games take flight,
And pop a Snackbar when shortcuts aren't right,
Hooray — cold starts now wake up bright! 🎮✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the primary changes: fixing cold-launch intent race conditions and adding failure feedback through snackbars and dialogs.
Linked Issues check ✅ Passed The PR fully addresses issue #883 by handling cold-start intents via pending launch storage, preventing silent failures, and adding user feedback for parse errors, uninstalled games, and Steam login requirements.
Out of Scope Changes check ✅ Passed All changes are within scope: MainActivity refactoring for intent handling, PluviaMain for pending launch processing, string resource additions for user feedback messages, and no extraneous modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/main/java/app/gamenative/MainActivity.kt`:
- Around line 231-249: Remove the early wasLaunchedViaExternalIntent = true
assignment and instead set wasLaunchedViaExternalIntent = true only when a
launch is actually committed: in the isNewIntent branch, set the flag after
consumePendingLaunchRequest() and immediately before emitting
AndroidEvent.ExternalGameLaunch(launchRequest.appId) inside
lifecycleScope.launch; in the cold-start branch, set the flag immediately when
calling setPendingLaunchRequest(launchRequest) so the pending state counts as a
committed launch. Update references to wasLaunchedViaExternalIntent accordingly
so it reflects only committed/active external launches.

In `@app/src/main/java/app/gamenative/ui/PluviaMain.kt`:
- Around line 333-339: The pending launch stored for Steam-login-required paths
only preserves appId (using IntentLaunchManager.LaunchRequest(appId =
event.appId)), which discards temporary overrides like
launchRequest.containerConfig; change the call to
MainActivity.setPendingLaunchRequest to pass the original full LaunchRequest
(the incoming event/launchRequest object) so containerConfig and other fields
are preserved across the login flow (i.e., stop constructing a new LaunchRequest
with only appId and store the original launchRequest/event instead).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d7d84b5-0c28-48b8-9ca1-c1de9e4a8a93

📥 Commits

Reviewing files that changed from the base of the PR and between 8571941 and 79f929f.

📒 Files selected for processing (16)
  • app/src/main/java/app/gamenative/MainActivity.kt
  • app/src/main/java/app/gamenative/ui/PluviaMain.kt
  • app/src/main/res/values-da/strings.xml
  • app/src/main/res/values-de/strings.xml
  • app/src/main/res/values-es/strings.xml
  • app/src/main/res/values-fr/strings.xml
  • app/src/main/res/values-it/strings.xml
  • app/src/main/res/values-ko/strings.xml
  • app/src/main/res/values-pl/strings.xml
  • app/src/main/res/values-pt-rBR/strings.xml
  • app/src/main/res/values-ro/strings.xml
  • app/src/main/res/values-ru/strings.xml
  • app/src/main/res/values-uk/strings.xml
  • app/src/main/res/values-zh-rCN/strings.xml
  • app/src/main/res/values-zh-rTW/strings.xml
  • app/src/main/res/values/strings.xml

On cold start, handleLaunchIntent fired before PluviaMain's ViewModel
registered its event listener. The fire-and-forget event bus dropped
the ExternalGameLaunch event. Now all cold-start intents use the
pending request mechanism; onNewIntent still emits directly.
Show snackbar when launch shortcut has invalid extras, and when
a Steam game requires login before it can launch.
Copy link
Contributor

@phobos665 phobos665 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks reasonable to me. Have you tested with all game types?

@jeremybernstein
Copy link
Contributor Author

Looks reasonable to me. Have you tested with all game types?

Steam, GOG, custom all work fine. I don't have Epic or Amazon accts set up, but they will use exactly the same Intents launch path.

@jeremybernstein
Copy link
Contributor Author

I did notice that, immediately after installing a game, if GN remains active and a launch intent is received, GN reports that the game can't be found. Definitely a different issue/PR (maybe a DB sync issue?), but capturing it here.

Comment on lines +194 to +199
private fun needsSteamLogin(context: Context, appId: String): Boolean {
val gameSource = ContainerUtils.extractGameSourceFromContainerId(appId)
if (gameSource != GameSource.STEAM || SteamService.isLoggedIn) return false
// offline-mode games can launch without Steam
return !ContainerUtils.hasContainer(context, appId) ||
!ContainerUtils.getContainer(context, appId).isSteamOfflineMode()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this required? Does it fail on cloud saves?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so, we might want to check if the user is signed in in offline mode too. PrefManager.isOffline I think

Comment on lines +277 to +322
LaunchedEffect(Unit) {
MainActivity.consumePendingLaunchRequest()?.let { launchRequest ->
Timber.i("[PluviaMain]: Processing pending launch request for app ${launchRequest.appId}")
// Steam games needing login will be handled by OnLogonEnded
if (needsSteamLogin(context, launchRequest.appId)) {
MainActivity.setPendingLaunchRequest(launchRequest)
SnackbarManager.show(context.getString(R.string.intent_launch_steam_login_required))
} else {
when (val resolution = resolveGameAppId(context, launchRequest.appId)) {
is GameResolutionResult.Success -> {
if (launchRequest.containerConfig != null) {
IntentLaunchManager.applyTemporaryConfigOverride(
context, launchRequest.appId, launchRequest.containerConfig,
)
}
MainActivity.wasLaunchedViaExternalIntent = true
trackGameLaunched(resolution.finalAppId)
viewModel.setLaunchedAppId(resolution.finalAppId)
viewModel.setBootToContainer(false)
preLaunchApp(
context = context,
appId = resolution.finalAppId,
useTemporaryOverride = launchRequest.containerConfig != null,
setLoadingDialogVisible = viewModel::setLoadingDialogVisible,
setLoadingProgress = viewModel::setLoadingDialogProgress,
setLoadingMessage = viewModel::setLoadingDialogMessage,
setMessageDialogState = setMessageDialogState,
onSuccess = viewModel::launchApp,
)
}

is GameResolutionResult.NotFound -> {
val appName = ContainerUtils.resolveGameName(resolution.originalAppId)
Timber.w("[PluviaMain]: Game not installed: $appName (${launchRequest.appId})")
msgDialogState = MessageDialogState(
visible = true,
type = DialogType.SYNC_FAIL,
title = context.getString(R.string.game_not_installed_title),
message = context.getString(R.string.game_not_installed_message, appName),
dismissBtnText = context.getString(R.string.ok),
)
}
}
}
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait can you explain why this was needed? what's the cold start?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure -- backing up to the old state: Steam games were deferred via:

  if (gameSource == GameSource.STEAM && !SteamService.isLoggedIn && !runsWithoutSteam) {
      setPendingLaunchRequest(launchRequest)  // defer until login
  } else {
      consumePendingLaunchRequest()
      emit(ExternalGameLaunch)  // launch now
  }

and all other game types were immediately consumed. The Steam pending request was consumed when the Steam-specific OnLogonEnded event fired. If GN wasn't running at the time this request arrived (cold-start), non-Steam game launches (and Steam launches for games which run without Steam login) would just fire into the void. Steam games would wait until login completes and then start.

This new consumePendingLaunchRequest makes the most sense in Pluvia because it's the earliest point where all requirements are met:

  • Compose UI is composed (can show snackbar/dialog feedback)
  • ViewModel is available (can call preLaunchApp)
  • Login state is known (can gate Steam games on login)

Compose doesn't exist in MainActivity.onCreate, so that's not the right place, either.

Does that make more sense?

@utkarshdalal utkarshdalal merged commit c6ccb1c into utkarshdalal:master Mar 18, 2026
2 checks passed
@jeremybernstein jeremybernstein deleted the jb/fix-intent-launch branch March 18, 2026 08:37
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.

Cold-launch intent silently fails for non-Steam games

3 participants