Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LHgWjmEJXsJEi1EVAoTp4K
7.3 KiB
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
versionNameinapp/build.gradle.ktsmoves 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), matchingversionName. - 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
BroadcastReceiverforDownloadManager.ACTION_DOWNLOAD_COMPLETEto know when the download finishes. - On completion, calls
DownloadManager.getUriForDownloadedFile(id)— this returns a ready-to-usecontent://URI with no customFileProvidersetup required (the earlier draft of this design needed one, which would have pulled in an AndroidX dependency;DownloadManageravoids that entirely). - Fires
Intent(ACTION_VIEW)with that URI, MIME typeapplication/vnd.android.package-archive, andFLAG_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:
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />- No new
<provider>entry (avoided by usingDownloadManager, 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-syncserver 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.