- Make semantic colors and the page indicator theme-aware so they keep
adequate contrast in dark mode instead of using fixed values.
- Seed a full brand color scheme (#6C63FF) for devices without dynamic
color (Android 11 and below) instead of falling back to Material's
purple baseline; align the XML AppTheme accent accordingly.
- Stop manually forcing transparent system bar colors in the Compose
theme; those setters are no-ops on Android 15+ and stripped the system
contrast scrim, causing the half-gray navigation bar on One UI. Keep
syncing the bar icon appearance with the active theme.
- Add a dynamic-color opt-out toggle (Android 12+), defaulting off on
One UI where Samsung's palette extraction tends to look off.
DeepLinkConfirmActivity shared the default task affinity with the main
Aurora task, so when the app was already running the system brought the
existing task to the foreground instead of launching the gate. Giving
the trampoline an empty task affinity decouples it from the main task,
so the confirmation sheet and target listing load every time.
Resolves#1494
Allow signed-in users to rate, review, edit, and delete an installed app's
review directly from its details screen. The form is hidden from anonymous
accounts and only shown for installed apps.
Submitted reviews are cached locally in a new Room "review" table (db v9) so
they appear immediately while Google publishes them, and are reconciled with
the Play API: edits made on the Play Store are mirrored, and a review deleted
there (or via the new delete action) is removed locally. Deletion relies on
gplayapi 3.6.3.
The install event isn't delivered when the app replaces its own APK, so a
self-update could linger in the Updates list as "installing forever" after the
update completed and the app restarted.
- UpdateHelper: clear a stale self-update row on launch, flavor-aware — nightly
by its commit-tagged version name (the version code is static), release/preload
by the version code.
- DownloadHelper: finalize a self-update download stuck in INSTALLING (the commit
killed the process before it could advance) to INSTALLED on launch.
The deep-link router matched "/apps/dev" as a substring, so publisher
links (/store/apps/developer?id=<name>) were treated as developer-stream
links and failed to resolve. Match the exact action keyword (last path
segment, or host for market:// links) instead, and add a
Screen.PublisherProfile that renders the publisher-search overload of
DevProfileScreen.
Both /dev and /developer links may carry either a numeric developer id
(curated developer stream) or a developer name (publisher search), so
the id is parsed to long to decide which screen to open.
Also drop the now-dead ACTION_VIEW branch from ComposeActivity, since
DeepLinkConfirmActivity owns all external VIEW deep links.
The DevProfileScreen(developerId) overload was an empty stub, so
play.google.com/store/apps/dev?id= deep links rendered a black screen.
Render the developer's stream bundle via the existing StreamCarousel,
observing DevProfileViewModel for loading, error/retry and per-cluster
pagination.
Ad networks can exploit Aurora's market:// and play.google.com VIEW
intent-filters to launch the app straight into a listing without user
intent (issue #1450). Route these external deep links through a new
translucent DeepLinkConfirmActivity that shows a Play Store-style
bottom sheet over the launching app; the listing only opens after an
explicit tap, defeating auto-launch regardless of referrer.
- DeepLinkConfirmActivity: resolves the listing, shows the sheet, then
forwards to ComposeActivity via the Screen parcel (Open) or finishes
(Cancel). Forwards directly when the user opts out.
- DeepLinkConfirmSheet: bottom sheet matching the existing sheets/
convention; shows the target id and the launching app when known.
- Move the VIEW intent-filters off ComposeActivity onto the new
activity, using a transparent edge-to-edge translucent theme.
- Add a "Confirm external links" toggle in Security (default on).
Addresses #1508
Add an 'Install all' action to the Favourites screen that bulk-fetches
details and enqueues every installable favourite (skipping already
installed apps and paid apps anonymous accounts can't acquire), gated
behind a confirmation dialog that warns about per-app install prompts.
Show live state on each favourite row: an installed tick that updates
on install/uninstall events, and download/install progress mirroring the
Updates screen. Hide 'Install all' once every favourite is installed.
Treat only non-finished download statuses as in progress on both the
Favourites and Updates rows so a dismissed system install prompt no
longer leaves a row stuck showing 'Installing'.
Resolves#1499
Surface a 'Buy @ price' action on the install error sheet for paid apps
on signed-in accounts, opening the Play Store listing to purchase. For
anonymous accounts, show the price on a disabled install button and
disable manual downloads since paid apps can't be acquired.
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.