# In-App Updates — Design Date: 2026-09-27 Status: approved, pending implementation plan ## Background KwabaTV has no update mechanism today — every new build requires a manual `adb install -r` sideload from a development machine. That's fine for this session's iteration pace, but not sustainable once the app is "done" and just sitting on a Fire TV Stick: any future fix requires physically reconnecting ADB. Kwaba Sync's own desktop Electron app (`desktop/`, in the `kwaba-sync` repo) already solved an equivalent problem: the server proxies the latest GitHub-style release from Gitea (`src/desktop-release.js`), gated behind a login because that repository is private, and the Electron app polls it, shows an in-window update banner, and never installs without an explicit click. KwabaTV's own Gitea repo (`GGB/KwabaTV`) is public — confirmed via its Gitea API (`visibility":"public"`, releases endpoint returns 200 unauthenticated). That removes the reason the desktop app's design needed a server-side proxy at all (private-repo auth). This design is intentionally simpler than desktop's as a result: no `kwaba-sync` involvement, no server-side changes, no coordination needed with that repo's owner. It mirrors desktop's *behavior* (check on launch, never install silently, graceful failure) without mirroring its *architecture*. ## Goal The app checks Gitea for a newer release on every launch, and — if one exists — offers to download and install it, entirely on-device. ## Release convention - `versionName` in `app/build.gradle.kts` moves from the current `"1.0"` to a proper 3-part semver (e.g. `"1.1.0"`) as part of this work, since the comparison logic below assumes major/minor/patch on both sides. - Tag format: `vX.Y.Z` (e.g. `v1.1.0`), matching `versionName`. - Exactly one APK asset per release, always named `KwabaTV.apk`, so the app never has to guess which asset to download. - Releases are published manually for now (no CI in this repo yet). Drafts and pre-releases are ignored by the version check. ## Components All new code lives in kwabatv; nothing else changes outside it. **`UpdateChecker.kt`** — a small, self-contained class with one entry point, e.g. `checkForUpdate(context, onAvailable: (UpdateInfo) -> Unit)`. On a background thread, it does a plain HTTP GET to `https://git.whodovoodoo.de/api/v1/repos/GGB/KwabaTV/releases`, picks the highest-versioned non-draft, non-prerelease release whose tag matches `vX.Y.Z`, and compares it numerically (major/minor/patch) against `BuildConfig.VERSION_NAME`. If the release is newer, it calls back with `{ version, apkDownloadUrl }` on the main thread. Any failure along the way (network unreachable, non-200, malformed JSON, no matching release) is swallowed silently — no error UI, no retry logic beyond "try again next launch." **`MainActivity.onCreate()`** calls `UpdateChecker.checkForUpdate(...)` right after `webView.loadUrl(BuildConfig.WEBSITE_URL)`, so the check never delays getting to content. If the callback fires, it shows a native `AlertDialog`: > **KwabaTV v1.1.0 ist verfügbar** > [Aktualisieren] [Später] Android's default dialog buttons are focusable and DPAD-navigable with no extra work. "Später" just dismisses; the same check runs again next launch, with no persisted "skip this version" state. **Download + install**, triggered by "Aktualisieren": - Uses `android.app.DownloadManager` (a base-SDK system service — no AndroidX needed, keeping the app's existing zero-dependency footprint) to download the APK asset directly from Gitea's public release URL. - Registers a `BroadcastReceiver` for `DownloadManager.ACTION_DOWNLOAD_COMPLETE` to know when the download finishes. - On completion, calls `DownloadManager.getUriForDownloadedFile(id)` — this returns a ready-to-use `content://` URI with no custom `FileProvider` setup required (the earlier draft of this design needed one, which would have pulled in an AndroidX dependency; `DownloadManager` avoids that entirely). - Fires `Intent(ACTION_VIEW)` with that URI, MIME type `application/vnd.android.package-archive`, and `FLAG_GRANT_READ_URI_PERMISSION`, which opens Android's own system package installer. The user taps "Install" themselves inside that system UI — Android does not allow silent unattended installs, by OS design, regardless of permissions granted. **First-run permission gate:** before firing the install intent, check `packageManager.canRequestPackageInstalls()`. If false (first time ever installing an update), send the user to `Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES` for this package instead, so they can grant "install unknown apps" for KwabaTV specifically. They then press "Aktualisieren" again to retry. **New manifest entries:** - `` - No new `` entry (avoided by using `DownloadManager`, see above). ## Data flow ``` App launch → webView.loadUrl(WEBSITE_URL) [not blocked by the check] → UpdateChecker (background thread) GET git.whodovoodoo.de/.../releases → newer release found? no → nothing happens yes → AlertDialog on main thread "Später" → nothing further this launch "Aktualisieren" → canRequestPackageInstalls()? no → Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES yes → DownloadManager.enqueue(apk) → ACTION_DOWNLOAD_COMPLETE → ACTION_VIEW install intent → system installer UI (user taps Install) ``` ## Error handling Every failure mode in the check itself (DNS/network failure, non-200, malformed JSON, no releases, no tag matching `vX.Y.Z`) results in no dialog and no visible change — identical to a launch where the app is already current. This matches desktop's graceful-degradation philosophy: an update check is a nice-to-have, never something that should be able to block or degrade normal app use. Download failures (`DownloadManager` reports a failed status) are also silent for now — the user can just press "Aktualisieren" again next launch, or manually sideload as a fallback (this doesn't remove that option). A visible failure toast is a reasonable future improvement, not required for the first version of this feature. ## Testing This feature cannot be meaningfully unit-tested in isolation — the interesting behavior is entirely about real network calls, real Android permission state, and the real system package installer. It will be verified end-to-end on the real Fire TV Stick used throughout this project: publish an actual test release on the `GGB/KwabaTV` Gitea repo, then run the full check → dialog → download → permission-gate → install flow on-device, the same hands-on verification approach used for every other feature this session. ## Out of scope (for this pass) - Any `kwaba-sync` server changes (Option B from the design discussion — hosting the APK for discovery from the Kwaba website's lobby — is a separate, later concern, not required for in-app updates to work). - CI/automated release publishing. - Periodic re-checks while the app stays open (check is launch-only). - "Skip this version" / persisted dismissal state.