feat(install): route the per-module install queue through InstallServ…#123
Merged
Merged
Conversation
…ice (ADFA-4476 slice 3) Slice 1 moved the module-grid selection to the Activity-scoped ViewModel; slice 2 moved the scratch reset into the service. This slice moves the last Fragment-bound long op -- the per-module install queue -- into the foreground InstallService, so the service (not the Fragment) is the single owner of the loop. Root cause it closes: the queue loop lived in InstallController with a Fragment-scoped re-entry flag (installInFlight), and DeployFragment.onResume() re-fired processNextInQueue() after a recreation. A theme toggle mid-install recreated the Activity, installInFlight was born false, and onResume launched a SECOND concurrent runrole in the same rootfs -> corruption. The ADFA-4519 stop-gaps (installingModuleKeys guard + in-flight display) mitigated it; this removes the cause: the service keeps advancing the queue regardless of the Fragment lifecycle, and there is never more than one runrole in flight. - ModuleQueueState + ModuleQueueRepository: app-scoped observable of the queue (current module, remaining, failed set, seq), mirroring InstallProgressRepository. Kept separate from InstallState because the queue has a different shape; the two ops are mutually exclusive at runtime. - InstallService: new ACTION_START_MODULES (+ EXTRA_MODULES) owns the dequeue loop: ModuleName.isAllowed() fail-closed (D2), the sed/echo/runrole command, the AnsibleRunOutcome verdict (non-zero OR error-in-stdout), exit 137 logged as a phantom-kill, revert-on-fail, and finish/fail reporting. Queue is persisted to iiab_queue_prefs as it drains. Cancel routes through the existing ACTION_CANCEL. - InstallController: processNextInQueue()/installInFlight/revertModuleInLocalVars/ saveQueueToPrefs removed; the launch button now just starts the service (startModuleQueue). verifyInstallationState() reads in-flight state from ModuleQueueRepository, never the half-written local_vars.yml. - DeployFragment: onResume() no longer re-fires the queue; it observes ModuleQueueRepository (renderModuleQueue) for the launch button + one-shot finish/fail snackbar, and drops installed modules from the selection on DONE. isBatchInstalling() is now derived from the repository (covers a queue started before a recreation), so mutual exclusion holds. - Supersedes the ADFA-4458/4519 re-entry guard and the installingModuleKeys set (removed from InstallHost + DownloadStateViewModel). The ADFA-4519 showSnackbar() fix is kept and reused. - Test: AnsibleRunOutcomeTest gains the exit-137 (phantom-kill) case. Stacks on ADFA-4519 (PR #120); rebases cleanly onto main once that merges.
e817b73 to
d11394e
Compare
Collaborator
Author
|
Validated on device — PR operates correctly and the theme-toggle regression is gone. With a two-module queue, toggling the theme repeatedly mid-install now keeps a single |
luisguzman-adfa
added a commit
that referenced
this pull request
Jul 3, 2026
…e-first (ADFA-4466) Wires Firebase Analytics as the analytics sink and treats the device as constantly online (the Firebase SDK buffers offline and flushes on reconnect). The shared store-and-forward backbone (ADFA-4504) and the Cloudflare Worker are out of scope for now; AnalyticsEvent/DeliveryManager stay in the tree as that parked backbone but are no longer on the analytics path. Foundation: - build.gradle: google-services plugin 4.4.2 (root classpath) + firebase-bom 33.7.0 + firebase-analytics (Java API) in :app. - AndroidManifest: disable Advertising ID / SSAID collection and ad personalization (K2Go may serve minors; operational analytics only). - CI: inject GOOGLE_SERVICES_JSON into android-sanity-check and android-release-build so the google-services plugin has google-services.json at build time. .gitignore keeps a real one from being committed. Client: - AnalyticsClient routed to FirebaseAnalytics.logEvent, consent-gated: every method is a no-op unless the operator opted in, and setAnalyticsCollectionEnabled(consent) keeps the SDK from gathering anything while OFF. install_id + binaries_tag are anonymous user properties; device/OS/app-version/country are Firebase's own automatic dimensions. - Existing events kept (first_run, app_opened, session) and now reach Firebase. - Phase-1 operational API added: install_started/completed/failed, server_started/stopped (enum + bucketed params only, never raw messages or exact durations). - install_started is wired at the rootfs-install kickoff. install_completed/failed and server_* call-sites are a small follow-up sequenced AFTER the in-flight InstallService PRs (#122 reset, #123 module queue) to keep this PR conflict-free and wire them at the correct op-aware points. - Consent is applied immediately at startup and on the Setup / About toggles. Tests: - AnalyticsBuckets (pure) + AnalyticsBucketsTest (JVM) for uptime bucketing. - AnalyticsConsentGateTest updated for the Firebase sink (consent default OFF; the gated emit path runs without throwing in both states — Firebase's buffer isn't introspectable on-device). Open item for the team: confirm which Firebase project to use (reuse the existing K2Go App Distribution project vs a dedicated telemetry project) and that it has a GA4 property with org.iiab.controller registered; the GOOGLE_SERVICES_JSON secret must point at it.
luisguzman-adfa
added a commit
that referenced
this pull request
Jul 3, 2026
Now that ADFA-4476 (#122 reset, #123 module queue) is merged, the InstallService terminal and the server-alive poll are stable, so the four Phase-1 events deferred earlier are wired at their correct, operation-aware points. Phase 1 is complete: all 8 K2Go events emit (consent-gated, no-op unless opted in). - install_completed: InstallService.finishSuccess(), gated to a real install (not reset/module mode), with tier + success=true. - install_failed: the four install-pipeline failure sites, each with its phase (download/extract) and a coarse reason_code (network/no_archive/extract_error/ exception) — never the raw message. Reset/module failures do not emit it. - server_started / server_stopped: a single updateServerAlive() chokepoint in MainActivity fires exactly on the false<->true transition; server_stopped carries a coarse uptime bucket, never an exact duration. Branch rebased onto current main (was cut from a stale #115 base).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…ice (ADFA-4476 slice 3)
Slice 1 moved the module-grid selection to the Activity-scoped ViewModel; slice 2 moved the scratch reset into the service. This slice moves the last Fragment-bound long op -- the per-module install queue -- into the foreground InstallService, so the service (not the Fragment) is the single owner of the loop.
Root cause it closes: the queue loop lived in InstallController with a Fragment-scoped re-entry flag (installInFlight), and DeployFragment.onResume() re-fired processNextInQueue() after a recreation. A theme toggle mid-install recreated the Activity, installInFlight was born false, and onResume launched a SECOND concurrent runrole in the same rootfs -> corruption. The ADFA-4519 stop-gaps (installingModuleKeys guard + in-flight display) mitigated it; this removes the cause: the service keeps advancing the queue regardless of the Fragment lifecycle, and there is never more than one runrole in flight.
Stacks on ADFA-4519 (PR #120); rebases cleanly onto main once that merges.