Skip to content

In-app update check and install for the foss build - #79

Open
jo2904 wants to merge 4 commits into
karatektus:mainfrom
jo2904:feat/in-app-updater
Open

jo2904 wants to merge 4 commits into
karatektus:mainfrom
jo2904:feat/in-app-updater

Conversation

@jo2904

@jo2904 jo2904 commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Closes #77

Opened together with the issue; happy to wait for accepted before anything is merged.

What changes

The foss build checks GET /repos/karatektus/pl_mail_android/releases/latest once a day and can install a newer release itself.

  • Check: UpdateCheckWorker, a daily PeriodicWorkRequest with a network constraint. It takes the *-foss.apk asset of the latest stable release (/releases/latest never returns pre-releases) and compares versionCodes computed with the same formula as the convention plugin.
  • Notice: one LOW notification per new release, on its own "App updates" channel. Tapping it opens Diagnostics.
  • Screen: a Version section at the top of Diagnostics, with the installed version, the release notes, a download-and-install button with progress, and a "Check for updates daily" switch. Opening Diagnostics makes a request only when the daily check has already announced a release, and never to the mail server.
  • Install: the APK is downloaded to filesDir/updates, checked (a PK header, the size, and the SHA-256 GitHub publishes in each asset's digest), then handed to the system installer with ACTION_VIEW through a FileProvider. Android confirms with the user and refuses an APK signed by another key. A checked download is kept, so reopening the installer does not download again.

Who gets it:

Build Updater
foss signed with the release key yes
foss debug, forks, F-Droid-signed no: the app compares its signing certificate with RELEASE_CERT_SHA256 (6076e3a3…, from apksigner on plmail-0.4.2-foss.apk), since Android would refuse the update anyway
google no: neither the code nor REQUEST_INSTALL_PACKAGES. GoogleUpdateBindings binds NoAppUpdater, the same seam as FcmSupport

docs/RELEASING.md gains a section on what this expects of a release: the asset suffix, no pre-releases, and the certificate constant to change if the key is ever replaced.

How it was checked

  • ./gradlew test locally: 1599 tests, 0 failures, including the new GitHubReleasesTest (release parsing against the real v0.4.2 response shape, flavour asset picking, drafts and pre-releases refused, versionCode parity with the convention plugin, digest parsing, file checks).
  • Both flavours compile (fossDebug, googleDebug).
  • Not yet run locally: ./gradlew build (lint). CI will tell.
  • Not yet on a device. The full path (download and install) only runs on a build signed with the release key, so on a debug build the section shows the version and the "updated by wherever it came from" note.

Note

#75 (French translation) landed first, so main is merged in and the 17 new strings now have French copies too (2d5a2ca). That commit was checked by a script for key coverage (none missing in values/, values-de/ or values-fr/) and by spotlessCheck; a local lint and build run was stopped for lack of memory, so CI's build is the check for it.

Checklist

  • Linked to an issue above
  • Tests for the change (./gradlew test)
  • Formatted and green: spotlessApply done, ./gradlew build left to CI
  • New or changed copy is in both values/strings.xml and values-de/strings.xml
  • Docs updated (docs/RELEASING.md)
  • Needs a server change? No
  • A setting that changes how the app behaves for everybody is off by default, or says why not: the daily check is on by default, because a sideloaded install that never hears about updates is the problem this solves. It only notifies, never installs on its own, and the switch is in Diagnostics.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CFCshXUp57k1yHeHy68JXy

…uild

Sideloaded installs had no way to learn that a release was out: the
GitHub APK is what most foss users run, and nothing told them about
0.4.1 or 0.4.2 short of watching the repository.

The foss build now asks /releases/latest once a day (WorkManager,
network constraint), posts one low-importance notification per new
release, and shows a Version section at the top of Diagnostics with the
installed version, the release notes, and a download-and-install
button. The APK is checked against the size and the SHA-256 digest
GitHub publishes for each asset, then handed to the system installer
with ACTION_VIEW: Android asks the user and refuses anything not signed
by the same key, which is the real guarantee. Same pattern as NewPipe
and Vela; no code taken from either.

Who gets it, and why the split is where it is:

- google flavour: nothing. Play forbids self-updating outside Play and
  restricts REQUEST_INSTALL_PACKAGES, so the permission and the
  FileProvider live in app/src/foss/AndroidManifest.xml and the google
  build binds NoAppUpdater, the same seam FcmSupport uses.
- foss builds not signed with the release key (debug, forks, F-Droid's
  own signature): the updater compares the app's signing certificate to
  RELEASE_CERT_SHA256 and stays off, since Android would refuse the
  update after a 30 MB download anyway. The constant is what apksigner
  prints for plmail-0.4.2-foss.apk.

Traps for whoever touches this next:

- versionCodeOf is duplicated from the convention plugin (build logic
  can't be a runtime dependency); GitHubReleasesTest pins both on the
  plugin's documented examples.
- The asset suffix -foss.apk is a contract with release.yml's renaming
  step. Renaming assets silently turns every updater off.
- UpdateFileProvider subclasses FileProvider because :core:data already
  declares androidx FileProvider for blobs, and two providers with one
  class name merge into one.
- The check runs on opening Diagnostics only when a release was already
  announced, so the screen still makes no requests just for being
  opened in the ordinary case. It never contacts the mail server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation area: settings Concerns settings area: push Concerns push labels Oct 27, 2026
jo2904 and others added 3 commits October 27, 2026 12:50
Conflict in docs/RELEASING.md only: karatektus#76/karatektus#81 added a "Google Play"
section in the same spot as this branch's "The in-app updater". Both
kept, Google Play first, since it follows the release in the order
things happen. Nothing merged from main touches the APK asset names
the updater relies on (-foss.apk) or any code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CFCshXUp57k1yHeHy68JXy
karatektus#75 landed first, so values-fr now exists in :core:notifications and
:feature:settings without the 17 strings this branch adds: the update
notification's channel, title and text, and the Version section of
Diagnostics. Lint's MissingTranslation would fail the build once this
is merged, although this pull request's own CI ran green before karatektus#75.

Same conventions as the rest of the French translation: "vous", the
typographic apostrophe, and a no-break space before ":" and "%".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CFCshXUp57k1yHeHy68JXy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: push Concerns push area: settings Concerns settings documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

In-app update check for the GitHub (foss) build

1 participant