Group the five channels under two headings in the system notification
settings so they're easier to navigate: "Downloads & updates" (progress,
installed, updates, export) and "Alerts" (errors and action-required).
Purely organisational; channel importances and behaviour are unchanged.
There was no in-app control over notifications. Add a Notifications
preference screen under Settings offering:
- "Show download progress": when off, downloads/updates no longer post
per-tick progress; a single quiet foreground notification keeps the
work running in the background (honouring the mandatory FGS rule).
- Deep links into each notification category's system settings (O+) so
users can tune sound and importance per channel.
- A shortcut to enable notifications when they're turned off at the
system level, so the screen degrades gracefully.
The download worker reads the progress preference live, keeping its
foreground notification minimal and skipping progress refreshes when
the user has opted out. POST_NOTIFICATIONS is already handled by the
onboarding permission flow, so no duplicate prompt is added here.
A bulk update produced one standalone notification per app for both
successes and failures, flooding the shade. Bundle these terminal
notifications into two groups, each with a collapsible summary that
lists the apps via InboxStyle:
- "N apps installed" on the (LOW, silent) installed channel, expiring
on its own since it's informational.
- "N apps failed" on the (HIGH) alerts channel, persisting until
handled and carrying a Retry action per app.
Summaries are rebuilt from the live posted notifications rather than
tracked state, so they stay correct across retries, dismissals and
process restarts, and are only posted on Android N+ where the system
actually renders groups.
Retry re-queues the download via a new DownloadRetryReceiver; the
worker resumes from verified files on disk, so retrying an install
failure re-installs without re-downloading.
The download worker posted a separate per-app notification for every
internal phase transition, keyed by package hash and distinct from the
ongoing foreground progress notification. In practice this meant:
- a blank, non-dismissable notification while "purchasing" (no branch
rendered it), and
- a transient "download complete" notice for every app that was then
immediately replaced by the install-success notification.
During a bulk update this produced a burst of redundant notifications.
Suppress the purchasing/verifying phases (the ongoing progress
notification already conveys activity) and only surface completion when
the user must act on it (apps that can't be installed silently get a
tap-to-install notice). Silently-installable apps now go straight to a
single "installed" notification.
Also clear any stale per-app notification on these phases and on
cancellation, fixing a leak where a cancelled download left its last
notification behind, and refresh the foreground notification to an
"Installing" state so the user sees a clean downloading -> installing
progression instead of a download bar stuck at 100%.
Notifications were too intrusive: install-success and export sat on
IMPORTANT_HIGH channels and produced heads-up alerts for every app,
while genuine download failures were buried in the IMPORTANCE_MIN
downloads channel where they were easily missed.
Restructure the channels around user intent:
- Download progress -> MIN, silent, no badge
- Installed apps -> LOW, silent (was HIGH)
- Updates available -> DEFAULT, silent
- App export -> LOW, silent (was HIGH)
- Errors & alerts -> HIGH (new, the only heads-up channel)
Failed downloads, installer-status errors and the unarchive auth prompt
now route to the high-importance alerts channel so they aren't lost,
while successful installs/exports stay quiet.
Android ignores importance edits on an already-created channel, so the
install/export channels get new IDs and the retired IDs (including the
folded-in account channel) are deleted on next launch.
Implements a full app lock (issue #1313) gating Aurora Store behind the
device biometric or, where unavailable, the screen-lock credential
(PIN/pattern/password) so it also works on TVs and older phones.
- AppLockManager: process-scoped, in-memory lock state so a cold start is
always locked, with a short background grace timeout to avoid
re-prompting during the install dialog or the biometric sheet itself.
- AppLockAuthenticator: BiometricPrompt wrapper using
BIOMETRIC_STRONG | DEVICE_CREDENTIAL on API 30+ and
setDeviceCredentialAllowed on older releases, plus an enrollment check.
- ComposeActivity (now a FragmentActivity) gates content via a three-state
machine (AUTHENTICATING/LOCKED/UNLOCKED); the authenticating state shows
a blank surface behind the prompt so dismissing it never flashes the
lock card. Re-locks on return past the timeout and sets FLAG_SECURE
while locked.
- Toggle lives on a dedicated Security preference screen, reached from
Settings like the other preference entries.
Address #1386
The Shizuku branch of getPreferredInstaller() already fell back to the
session installer, but the guard hasShizukuPerm() called
Shizuku.checkSelfPermission(), which throws when the binder is not alive
(Shizuku disabled). The exception escaped before the fallback could run,
so installs failed instead of falling back.
- Guard hasShizukuPerm() on Shizuku.pingBinder() and swallow failures so
it returns false (instead of throwing) when Shizuku is unavailable,
letting the existing fallback run. This also fixes canInstallSilently().
- Detect when any chosen installer resolves to the default and optionally
inform the user via a toast, gated by notifyOnFallback so cancel/remove
callers stay silent while real install paths surface it.
Address several gaps in the app export feature (#1132):
- Bundle shared-library APKs and OBB/patch files, preserving their
relative layout (libraries/<pkg>/ and Android/obb/<pkg>/).
- Fall back to the installed app's on-device APKs when the cached
download was cleared (e.g. auto-deleted after install), guarded by an
exact version match so a different installed version is never exported.
- Guard against the download row being replaced by a manual download of
another version between enqueue and execution.
- Fail (and delete the partial/empty document) instead of reporting
success when there is nothing to bundle or an error occurs.
- Store entries uncompressed and use larger buffers, so multi-GB exports
are no longer bottlenecked on pointless re-compression.
- Report progress through the foreground notification (alert once, then
silent) and dedupe concurrent exports for the same app via unique work.
- Check free space before downloading (only the not-yet-fetched bytes) and, on
Android O+, use StorageManager.getAllocatableBytes/allocateBytes so the system
can evict its cache; fail fast with a clear message instead of dying mid-write.
SessionInstaller sets a SessionParams size hint so staging can reserve space
too.
- Verify each APK at most once: files already verified during the download pass
are skipped in the final verification gate. Prefer SHA-256 and log SHA-1
fallback.
- An expired download URL (403/410) now clears the stored file lists and retries,
re-purchasing fresh URLs instead of repeatedly failing on the dead one.
- Cancel promptly mid-file rather than only between files, and cancel copyTo's
progress timer in a finally to avoid leaking the timer thread.
- Download.canInstall now requires a real .apk on disk; DownloadStatus
finished/running are Sets.
A download row previously stopped at COMPLETED and never reflected whether the
app actually installed. Add INSTALLING/INSTALLED states driven by a central
installer-event observer in DownloadHelper:
- COMPLETED -> INSTALLING on the installer's first progress event
- -> INSTALLED on success (row kept so the APK can still be exported)
- a failed install reverts INSTALLING -> COMPLETED so it can be re-installed
without re-downloading
Consumers that branched on COMPLETED are updated (App Details state, MicroG
status mapping, Downloads list icon). downloadStatus is stored as TEXT so no
schema migration is needed.
Cancelling a download cancelled the worker and marked the row CANCELLED but
never abandoned a PackageInstaller session that had already been staged for
install, leaking it. Add IInstaller.cancelInstall (no-op default, implemented by
SessionInstaller) and call it from DownloadHelper.cancelDownload.
Cross-process session persistence/reconciliation (committing a session staged
before a restart) is left as a follow-up; the startup session cleanup remains,
and is now cheap to recover from since CacheWorker keeps the downloaded files.
- CacheWorker no longer purges the files of a download that is still in-flight
or downloaded and awaiting install, so missing the system install prompt for
longer than the cache window no longer forces a re-download.
- observeDownloads now starts the next queued download only once nothing else
is purchasing/downloading/verifying, instead of merely checking for a
DOWNLOADING row. This serializes downloads so concurrent workers can't clobber
the shared foreground/progress notification.
- enqueue() no longer resets an active/verifying download back to QUEUED, so
the periodic update check and repeated taps can't trigger needless
re-downloads; when files are already downloaded & verified it installs
directly instead of re-running the pipeline.
- DownloadWorker only purges partial files on a genuine user cancellation;
system-initiated stops (lost connectivity, quota) keep partials and retry,
fixing downloads appearing to restart on a flaky network.
- Add NetworkType.CONNECTED constraint + exponential backoff and return
Result.retry() for transient/network failures (capped) instead of always
succeeding.
- Validate HTTP 206 before resuming a .tmp (overwrite on 200) and drop corrupt
files on verification failure so retries restart clean.
Fold Aurora Store's self-update back into the existing update pipeline
instead of a dedicated sheet/viewmodel/helper. The feed entry is mapped
to a regular App and added to the update list, reusing the standard
download + install path; it is never auto-installed silently.
- Add a dedicated "Self-update" section in the Updates tab with the
app-details navigation disabled (it's served from the Aurora OSS feed).
- Gate eligibility via PackageUtil.isSelfUpdateSupported(): vanilla/preload
flavors, not debug, not F-Droid (huawei excluded by flavor).
- Add a build-aware Settings toggle as the opt-out, removing the row
immediately when disabled.
- Exempt nightly self-updates from deleteInvalidUpdates (static version
code) and drop any stale self-update row when none is offered, so a
phantom update doesn't linger after a self-install.