feat(android): in-app APK update install (audit v3 #25)

Wires the "Install vX.Y.Z" action the About card's update-check
teed up.

* ApkInstaller @Singleton — downloads the server APK via the shared
  OkHttpClient (inherits auth cookie + BaseUrlInterceptor host
  rewrite; apkUrl is server-relative) into cacheDir, then launches
  the system installer via a FileProvider content:// URI. canInstall()
  gates on PackageManager.canRequestPackageInstalls() on O+, and
  requestInstallPermission() opens the "install unknown apps" settings
  page when not yet granted.
* Manifest: REQUEST_INSTALL_PACKAGES permission + FileProvider
  (${applicationId}.fileprovider) + res/xml/file_paths.xml exposing
  the cache dir.
* AboutCardViewModel.install(info): permission check → download →
  launch, with isInstalling + installMessage state. Errors routed
  through ErrorCopy.
* About card shows an "Install vX.Y.Z" button under the check button
  when an update is available, "Downloading…" while in flight, and
  the install message line. Extracted UpdateControls / InstallButton /
  ButtonSpinner helpers to keep AboutCard under the length cap;
  added @file:Suppress(TooManyFunctions) for the settings-card density.

Closes audit v3 #25 — the last open parity item from the v3 sweep
aside from the offline-pool Home cards (#28).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 01:18:49 -04:00
parent 5ce9ca9e62
commit 8f5635c489
5 changed files with 206 additions and 24 deletions
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!-- The downloaded update APK lives in the app cache dir; the
FileProvider exposes just that directory to the system
installer via a content:// URI. -->
<cache-path
name="updates"
path="." />
</paths>