Skip to content

CCCT-2441 Silent App Launch For Job Intro And Download Screens#3765

Open
conroy-ricketts wants to merge 9 commits into
masterfrom
CCCT-2441-new-login-path-for-intro-and-download
Open

CCCT-2441 Silent App Launch For Job Intro And Download Screens#3765
conroy-ricketts wants to merge 9 commits into
masterfrom
CCCT-2441-new-login-path-for-intro-and-download

Conversation

@conroy-ricketts

@conroy-ricketts conroy-ricketts commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

CCCT-2441

Product Description

Completes the rollout of the silent Connect app-launch path to its last two surfaces: starting a job from the opportunity intro screen, and the launch that runs right after an app finishes downloading/installing. These now open the app directly behind the single progress dialog — no LoginActivity / SeatAppActivity flashing through — and backing out of the app returns to the originating Connect screen rather than the transient setup screen. After repeated launch failures (two retries), the failure dialog now shows a dismiss-only "try again later" message instead of offering endless retries.

Here's a video of the retry prompt dialog:

Screen_Recording_20260617_142435_CommCare.Debug.mp4

Here's a video of the download and job intro pages. Note that I show two different cases: 1) the app was pre-installed and thus the download page is skipped, 2) the app was not pre-installed and we show the download page:

Screen_Recording_20260617_133207_CommCare.Debug.mp4

Technical Summary

  • Migrates ConnectJobIntroFragment and ConnectDownloadingFragment onto ConnectAppLaunchController, the shared silent-launch orchestration already used by the other Connect surfaces.
  • Removes the now-dead IS_LAUNCH_FROM_CONNECT flag, the appLaunchedFromConnect auto-login branch in LoginActivity, and the CONNECT_MANAGED_LOGIN / redirectToConnectHome back-out path in DispatchActivity — none have remaining writers once these two surfaces stop using the legacy path.
  • Hardens the launcher: bails if the fragment view is already gone; makes the "already logged into the app" shortcut sound (verifies the active session's key record belongs to the seated app, fixing a back-to-back-launch crash); and defers the back-stack pop until Home is in front to avoid a blank-frame flash.
  • LaunchOutcomeRouter escalates a retryable failure to a dismiss-only message once MAX_LAUNCH_ATTEMPTS (3) is reached.

Safety Assurance

Safety story

What gives me confidence:

  • I tested on a device: launched apps from both the job-intro Start button and the post-download surface, including launching several not-yet-installed apps back-to-back, landing on Home with no login/app-setup screens flashing through.
  • I reproduced the back-to-back NoSuchElementException crash before the session-shortcut fix and confirmed it no longer occurs after.
  • I exercised the new post-2-retries escalated dialog on a device by temporarily forcing the launch to always error.
  • The routing/escalation logic is covered by LaunchOutcomeRouter and ConnectAppLauncher unit tests, and the change is scoped to the Connect launch path.
  • The removed flag and branches are dead code — I verified there are no remaining writers/readers once the two surfaces are migrated.

Risks to review:

  • Touches the churn-prone LoginActivity / DispatchActivity by deleting the appLaunchedFromConnect auto-login and CONNECT_MANAGED_LOGIN / redirectToConnectHome back-out paths. This is behavior-preserving (those paths were only reachable via the now-removed flag) but worth a careful read.
  • The dismiss-only failure dialog and the controller's lifecycle / back-stack handling are Android-bound and not unit-tested; they were verified manually on-device.
  • The Hausa and Tigrinya translations for the new strings are best-effort and should get a native-speaker review.

Automated test coverage

LaunchOutcomeRouterTest covers the retry → escalation threshold (below the limit prompts retry; at the limit shows the persistent error). The existing ConnectAppLauncherTest covers the launcher's seat / login / outcome seams. The fragment-side dialog and lifecycle code is not unit-covered.

conroy-ricketts and others added 9 commits June 16, 2026 14:49
[AI] Migrated the job-intro and downloading screens to the silent Connect launch path and removed the now-dead IS_LAUNCH_FROM_CONNECT flag and its LoginActivity/DispatchActivity auto-login branches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Guarded the Connect app-launch controller against a destroyed fragment view so an in-flight launch fired from an async callback can no longer crash with IllegalStateException.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Collapsed the redundant nested seated-app login check into a single condition and trimmed the login-engine doc to describe only the current Connect launch behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Made the job-intro and download launch surfaces drop themselves from the Connect back stack after a successful launch, deferring the pop until Home is in front so no blank frame flashes on the way to Home.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Fixed a crash when launching freshly-installed Connect apps back-to-back: the already-logged-in shortcut now confirms the active session belongs to the seated app, so an install that re-seats a different app while a prior session is alive no longer skips re-login.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Stopped offering endless retries on a failing Connect app launch: after the worker retries twice, the dialog now shows a dismiss-only "check your connection and try again later" message instead of another Retry prompt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[AI] Added QA notes for the job-intro and post-download Connect launch surfaces, back-to-back launch stability, and the after-2-retries persistent-error dialog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@conroy-ricketts conroy-ricketts self-assigned this Jun 17, 2026
@conroy-ricketts

Copy link
Copy Markdown
Contributor Author

Suggested Review Order

  • app/src/org/commcare/connect/ConnectAppUtils.kt — start with the deleted launchApp (the legacy path being replaced)
  • app/src/org/commcare/connect/ConnectConstants.java, app/src/org/commcare/utils/FirebaseMessagingUtil.java — removal of the dead IS_LAUNCH_FROM_CONNECT / CONNECT_MANAGED_LOGIN constants and import
  • app/src/org/commcare/activities/LoginActivity.java — removal of the now-unreachable appLaunchedFromConnect auto-login branch
  • app/src/org/commcare/activities/DispatchActivity.java — removal of the matching CONNECT_MANAGED_LOGIN / redirectToConnectHome back-out path
  • app/src/org/commcare/connect/LaunchOutcomeRouter.kt — the LaunchActions seam + new retry-escalation decision
  • app/src/org/commcare/connect/ConnectAppLauncher.kt — silent launcher; note the sound "already logged in" check
  • app/src/org/commcare/connect/ConnectAppLaunchController.kt — fragment orchestration: view guard, deferred back-stack pop, persistent-error dialog, failure counting
  • app/src/org/commcare/fragments/connect/ConnectJobIntroFragment.java, app/src/org/commcare/fragments/connect/ConnectDownloadingFragment.java — the two migrated call sites
  • app/unit-tests/src/org/commcare/connect/LaunchOutcomeRouterTest.kt — escalation-threshold coverage
  • app/res/values/strings.xml (+ 9 locale files) — new dialog strings and translations
  • docs/commcare/login-engine.md — updated behavior docs

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors the Connect app launch flow by removing legacy "launched from Connect" state tracking from DispatchActivity and LoginActivity, deleting the IS_LAUNCH_FROM_CONNECT constant and ConnectAppUtils.launchApp() function, and renaming CONNECT_MANAGED_LOGIN to PERSONALID_MANAGED_LOGIN. All fragment-side launch call sites (ConnectDownloadingFragment, ConnectJobIntroFragment) are migrated to ConnectAppLaunchController. The controller gains retry-attempt tracking, a popLaunchingScreenOnSuccess flag that pops the Navigation back stack after Home launches, and a persistent-error dialog. LaunchOutcomeRouter introduces MAX_LAUNCH_ATTEMPTS=3 to escalate from a retry prompt to a dismiss-only error dialog. ConnectAppLauncher adds UserKeyRecord validation to its session check. Persistent-error dialog strings are added across ten locales.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • dimagi/commcare-android#3738: Modifies LoginActivity to change how Connect-managed login behavior is determined, directly intersecting with the Connect-launched state removal in this PR.
  • dimagi/commcare-android#3743: Modifies DispatchActivity's Connect/login managed-login flow and sets connectManagedLogin, which this PR removes.
  • dimagi/commcare-android#3753: Modifies LaunchOutcomeRouter.kt dispatch routing logic for Connect launch outcomes, directly overlapping with the failedAttempts escalation added here.

Suggested labels

skip-integration-tests

Suggested reviewers

  • OrangeAndGreen
  • shubham1g5
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% 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
Title check ✅ Passed The title accurately describes the main change: migrating app launches from job-intro and download screens to the silent launch path.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description fully addresses the template with comprehensive Product Description, Technical Summary, and Safety Assurance sections including detailed testing and risk analysis.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch CCCT-2441-new-login-path-for-intro-and-download

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.

@conroy-ricketts conroy-ricketts marked this pull request as ready for review June 17, 2026 18:47
@conroy-ricketts conroy-ricketts requested review from a team, Jignesh-dimagi, OrangeAndGreen and shubham1g5 and removed request for a team June 17, 2026 18:47
@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 25.86%. Comparing base (126182b) to head (e8d7c84).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3765      +/-   ##
============================================
- Coverage     25.87%   25.86%   -0.02%     
  Complexity     4389     4389              
============================================
  Files           950      950              
  Lines         57186    57189       +3     
  Branches       6810     6809       -1     
============================================
- Hits          14799    14792       -7     
- Misses        40558    40569      +11     
+ Partials       1829     1828       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@OrangeAndGreen OrangeAndGreen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[FIXED] The code changes look great but I'm seeing a functionality issue. Here are steps to reproduce:

  • Go to the opportunity list and press Resume on an opp where the learn/deliver app needs to be downloaded
  • Wait while the Download page appears, the app downloads, and logs in
  • Once on App Home, navigate back (i.e. swipe)

The code should navigate to the opportunity list, but instead it navigates to the Download page (showing complete progress)

@OrangeAndGreen

OrangeAndGreen commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

[FIXED] I'm also getting a repeatable crash that seems like it might be related to these changes.

Steps:

  • Go to opportunity list, Resume on a job that requires download
  • Once that completes, navigate back to opportunity list (through the download page as described by the previous bug)
  • Now resume a different opp that requires download
  • Wait for the download to complete and login to succeed
  • The app crashes loading App Home

Here's the exception:
java.util.NoSuchElementException: No record in table user_key_records for ID 1
at org.commcare.models.database.SqlStorage.readBytes(SqlStorage.java:455)
at org.commcare.models.database.SqlStorage.read(SqlStorage.java:446)
at org.commcare.services.CommCareSessionService.getUserKeyRecord(CommCareSessionService.java:476)
at org.commcare.CommCareApplication.getRecordForCurrentUser(CommCareApplication.java:1000)
at org.commcare.activities.StandardHomeActivity.preparePinMenu(StandardHomeActivity.java:182)
at org.commcare.activities.StandardHomeActivity.onPrepareOptionsMenu(StandardHomeActivity.java:172)
at android.app.Activity.onPreparePanel(Activity.java:4480)
at androidx.activity.ComponentActivity.onPreparePanel(ComponentActivity.java:511)
at androidx.appcompat.view.WindowCallbackWrapper.onPreparePanel(WindowCallbackWrapper.java:99)
at androidx.appcompat.app.AppCompatDelegateImpl$AppCompatWindowCallback.onPreparePanel(AppCompatDelegateImpl.java:3097)
at androidx.appcompat.view.WindowCallbackWrapper.onPreparePanel(WindowCallbackWrapper.java:99)
at androidx.appcompat.app.ToolbarActionBar$ToolbarCallbackWrapper.onPreparePanel(ToolbarActionBar.java:523)
at androidx.appcompat.app.ToolbarActionBar.populateOptionsMenu(ToolbarActionBar.java:457)
at androidx.appcompat.app.ToolbarActionBar$1.run(ToolbarActionBar.java:57)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1339)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1348)
at android.view.Choreographer.doCallbacks(Choreographer.java:952)
at android.view.Choreographer.doFrame(Choreographer.java:878)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1322)
at android.os.Handler.handleCallback(Handler.java:958)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at android.app.ActivityThread.main(ActivityThread.java:8177)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)

Comment thread app/src/org/commcare/activities/DispatchActivity.java
Comment on lines +84 to +86
if (fragment.view == null) {
return
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does this achive something else than fragment.isAdded() check ? If not, it would be nice to be consistent here and use the isAdded() check itsef.

.buildHomeLaunchIntent(activity)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP),
)
if (target.popLaunchingScreenOnSuccess) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this happen before the call to startActivity, given we are adding a lifecycle listener here and doing it after startActivity runs the risk of listener getting registered after the lifecycle change.

launchDialog = null
}

private fun popLaunchingScreenOnceHidden() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this creates a pre-condition for this class that all callers implement a NavHostFragment , think we should validate this at constructor level and throw an IllegalStateEx if there is noNavHostFragment .

boolean appInstalled = AppUtils.isAppInstalled(appId);
if (appInstalled) {
new ConnectAppLaunchController(ConnectJobIntroFragment.this).launchApp(appId, true);
new ConnectAppLaunchController(ConnectJobIntroFragment.this).launchApp(appId, true, true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reg. back behaviour here, does The Job Intro page do not appear once user goes into the app ? If so, is that because of an underlying opportunity state change that happens when we launch the App from this page ?

Comment on lines +55 to +60
val sessionUuid = instance.session.userKeyRecordUUID ?: return false
return instance
.getAppStorage(UserKeyRecord::class.java)
.getRecordsForValue(UserKeyRecord.META_SANDBOX_ID, sessionUuid)
.isNotEmpty()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this check should strictly happen with the Currently logged into Personal ID user id to establish that it's the current personal ID user that's logged into the app and not just any user.

appId: String,
isLearning: Boolean,
) = launch(LaunchTarget(appId, isLearning))
popLaunchingScreenOnSuccess: Boolean = false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't like that the controller here is manipulating the fragment stack and think it's extending it's responsibilities more than it need to, instead the calling fragment should handle it's own state i.e. it should finish itself on Resume when there is an undelying state change after pressing back that makes that fragment no longer relevant.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, I agree with that comment. I think the best approach is to have ConnectAppLaunchController -> handleLaunchOutcome inform the calling fragment of the final outcome, and let that fragment manage its own lifecycle. Maybe we can use an abstract method in BaseConnectFragment to handle this, as the controller is currently overloaded with lifecycle and fragment management responsibilities.

appId: String,
isLearning: Boolean,
) = launch(LaunchTarget(appId, isLearning))
popLaunchingScreenOnSuccess: Boolean = false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes, I agree with that comment. I think the best approach is to have ConnectAppLaunchController -> handleLaunchOutcome inform the calling fragment of the final outcome, and let that fragment manage its own lifecycle. Maybe we can use an abstract method in BaseConnectFragment to handle this, as the controller is currently overloaded with lifecycle and fragment management responsibilities.

@Jignesh-dimagi

Copy link
Copy Markdown
Contributor

@conroy-ricketts
I am facing another issue, which may be unrelated to these specific changes and likely from a previous PR. The navigation flow is as follows: Connect Opportunity List -> click on resume → App Home Screen → press back → Connect Opportunity List (again) → press back → the App Home Screen opens again.

@OrangeAndGreen

Copy link
Copy Markdown
Contributor

Noting the two issues I reported are resolved with the latest code

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.

4 participants