android / Build + lint + test (push) Successful in 3m54s
Replaces the ACTION_VIEW + application/vnd.android.package-archive handoff with a PackageInstaller session, and declares UPDATE_PACKAGES_WITHOUT_USER_ACTION so the update can land with no confirm dialog at all. The platform grants the silent path when the installer opts in via setRequireUserAction(USER_ACTION_NOT_REQUIRED), the installed app targets API 29+, the installer holds that permission, and the target is the installer itself. Minstrel updating Minstrel satisfies all four. Where it can't be granted — anything pre-S — the platform returns STATUS_PENDING_USER_ACTION and we show its dialog instead, so this degrades rather than failing. Prior art: Mihon, which is out-of-store and self-updating and whose updates are quiet for exactly this reason. It also confirmed REQUEST_INSTALL_PACKAGES is not what draws install warnings — Mihon declares it too. No setRequestUpdateOwnership(true), despite it reading like the obvious declaration for a self-updater. Ownership can only be claimed on initial installation (a no-op on update) and additionally wants the privileged ENFORCE_UPDATE_OWNERSHIP permission. It's an API for app stores claiming the apps they install. Also: the install now has an outcome. The old path fired an intent and assumed, so a failure and a user declining were indistinguishable. Sessions report back, so InstallOutcome distinguishes Installed / Cancelled / Failed, and cancelling returns to IDLE rather than showing an error — the user chose it. DOWNLOADING and INSTALLING became separate stages because the install half now genuinely waits, and "Downloading…" through a confirm dialog is a lie. The FileProvider and res/xml/file_paths.xml are gone. They existed only to expose the cached APK as a content:// URI for the old intent; a session takes a stream. Nothing else used that authority. Two judgement calls worth naming: - The pending-user-action intent is only launched if it resolves to a system component. Below API 34 a dynamically registered receiver can't declare itself unexported, so another app can broadcast at us, and an unchecked startActivity on an attacker-supplied extra would be an escalation primitive. The real confirm activity is a system app, so the check costs the legitimate path nothing. - Cancellation unregisters the receiver but deliberately does NOT abandon the session. By then it's committed, and killing an install because the user navigated away from the banner misreads their intent. Untestable here: no androidTest source set and no Robolectric, so the gesture-level behaviour is operator on-device verification.