# 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.