Thor v1.94.0 Release Notes
The first stable since v1.93.0, consolidating everything that shipped through the
v1.93.1, v1.93.2 and v1.93.3 pre-release builds — 67 pull requests.
Two threads run through it. The first is new ground: freeze profiles, bulk backup and
.xapk export, permission filtering, and a support catalogue that comes from Play instead of
from the binary. The second is quieter and, for anyone who relies on Thor to actually do what
it says, more important: a long sweep making privileged actions report the truth. pm and
am return exit code 0 for work they did not do. Thor used to believe them. It no longer does
— it re-reads the state it asked for, and tells you when the answer is no.
The same sweep found the sharpest bug in the release: freezing a preinstalled app was
uninstalling it, and taking its data with it.
✨ Highlights
- 🧊 Freeze Profiles — save named groups of apps and freeze or unfreeze the whole set in
one tap. - 💾 Bulk backup and export to a folder you choose, now including
.xapkalongside
.apkand.apks. - 🔎 Filter the app list by permission — Camera, Microphone, Location and more.
- 🧊 Freezing a preinstalled app no longer wipes its data. Thor disables it; where it must
fall back to removal, it keeps the data. - ✅ Freeze, restrict, force-stop and clear-data are judged by a readback, not by an exit
code that never meant success. - 🔒 App lock covers cold start, and the app list no longer leaks into the Recents preview.
- 📱 One app-info sheet, with the full details built in — the detailed-view switch is gone.
- 🏠 The home shortcuts pack themselves to the space they have, and the Installer and
Extensions tiles can be switched off in Settings. - 💖 Support tiers are read from Play, so a new tier appears without an app update.
What's Changed
🧊 Freeze Profiles (#295 — the profiles half of GH#55)
Named, saved groups of apps. Build a profile once, then freeze or unfreeze all of it from a
single control. The editor takes a bulk selection, and profiles persist in Room alongside the
watchlist.
💾 Bulk backup and .xapk export (#293 — GH#51 phase 1, and GH#164)
Export several apps in one run to a folder you pick, and export as .xapk alongside .apk
and .apks. Backups carry a manifest.json with a schemaVersion, so a later release can add
per-app data without invalidating a folder written by this one.
Scope, stated plainly: this is the APK half. Backing up an app's data is root-only and has
not been built. Exported bundles go through the normal share sheet, so Quick Share already moves
them phone-to-phone — GH#51 was closed by its reporter on that basis, not because the data half
landed.
🔎 Filter the app list by permission (#294 — for GH#285)
Filter by what apps can actually reach: Camera, Microphone, Location and the rest.
📱 Unified app-info sheet (#288)
App info and app details are one sheet. The "detailed view" toggle it replaces is retired
rather than hidden.
🏠 An adaptive home grid, with tiles you can hide (#357 — for GH#344)
The home shortcuts pack to the room they have instead of to a fixed 2×2. An odd number of tiles
leads with a full-width one rather than leaving a hole, and in the navigation rail — where a
tile has no width for its label — each takes its own row and explains itself on a long press.
Installer and Extensions each got a switch in Settings → General. Hiding a tile
takes away the shortcut, never the feature: the installer still handles APK intents, and
Extensions keeps its own Settings entry. The two preferences stack with the existing eligibility
rules rather than overriding them, so asking for the Extensions tile without a privilege mode
still shows nothing — and hiding both with no privilege legitimately empties the grid, which
disappears rather than leaving a gap where it used to be.
🧊 System-app freeze: disable first, remove second (#314, #332 — for GH#316)
The important fix in this release. Freezing a preinstalled app went down an uninstall path that
destroyed its data.
- Thor now disables a system app to freeze it (#314).
- Where a rung must fall back to removal, it uses
pm uninstall -kto keep data and cache. - The destructive rung is gated on an actual refusal, not on an Android version guess.
- The same defect existed on the Dhizuku path and was fixed there too (#332).
- Extension-initiated freezes are routed through the same
FreezeTiergate, so an extension
cannot take the destructive path around it.
❄️ Freezer reliability — tile, shortcuts, watchlist, bulk runs (#284, #286, #287, #300, #308, #331)
- The QS tile derives its state from real per-app freeze state instead of a cached guess
(#284). - Pinned shortcut icons are rebuilt by the runner rather than by whichever caller happened
to trigger it (#286). - The tile and Freeze-all no longer bypass the unsafe-app block (#287).
- Shortcut lookups use the flags a frozen system app needs, or the package reads as absent
(#300). - Removing an app from the watchlist always restores it (#308 — for GH#310), and says so
when it cannot. - Bulk removal stops reporting a success it never checked (#331).
✅ Privileged actions judged by a readback (#346, #347, #348)
- Actions are verified by re-reading the state, not by trusting an exit code (#347).
- Clear-data waits for the observer instead of reporting a wipe that was merely dispatched
(#348). - The app-ops fallback stops reporting a restriction it never applied.
- Every privileged command names the user, per that command's own AOSP default (#346) —
work profiles and secondary users were being hit by accident. - One unresponsive ROM no longer costs a batch ~15 seconds per app.
🤖 Dhizuku, on real hardware (#333)
Device testing turned up three defects, all fixed:
am get-current-useris denied to the Dhizuku uid, so the user id is resolved once and
centrally instead of being asked for per command.- The privilege dialog names Dhizuku in all five locales.
- Refresh never re-probed privileges at all. It looked Dhizuku-specific only because
Shizuku's listeners self-heal and mask it — root was affected too.
⏸️ Suspend ownership (#330)
An unsuspend you don't own returns an empty failure array, which every caller read as
success. Thor now reads the recorded suspender and reports honestly when it cannot lift a
suspension another privilege mode applied.
🔒 App lock, privacy and stability (#292, #299, #339, #340)
- App lock covers cold start, and the app list no longer appears in the Recents preview
(#340). - The lock has a way out when no prompt can succeed (#292) — a restore onto a device with
no enrolled biometric used to lock the user out of Settings entirely. - The "already prompted" flag is kept off cloud backup (#299), so a reinstall does not
pre-suppress the watchlist recovery prompt. - An unreadable settings file no longer crash-loops the app (#339).
📱 App list, installer and locale (#276, #277, #278, #329, #343, #345)
- The app list survives a ROM revoking package visibility in the background (#329) — it was
collapsing to a single entry on some Chinese-market ROMs. - The installer makes the confirmed identity and the installed bytes one set (#343), and
cancels a superseded parse so it reclaims its own staged copy. - Downgrade detection is gated on a known
versionCode, and the codes are surfaced (#276). longVersionCodeis no longer truncated toInt(#277).- Pull-to-refresh is no longer charged the transition settle delay (#278).
- The in-app language picker works below API 33 (#345), and a language change reaches the
caches that hold copies.
💖 Support (#311, #342, #351, #356)
- GitHub Sponsors alongside Patreon and PayPal (#311).
- Ko-fi and Buy Me a Coffee are now in the app (#356). The website and
FUNDING.ymlhave
advertised five funding routes for a while; the in-app sheet only ever offered three. All five
now agree. - Support tiers are probed rather than hardcoded (#351). Play exposes no catalogue-
enumeration API, so Thor queries a candidate ID set and renders whatever Play answers with —
a tier added in Play Console shows up without an app release. Prices and their ordering come
from Play's own localized figures, so the amount shown is the amount in the buyer's currency. - A resume rebuilds a billing connection the retry ladder had given up on (#342).
🌐 Project: the site, the stores, the build
- The Thor landing page shipped (#313, #319, #321, #322, #327, #328) — an Astro site with a
claims gate that reads the built HTML rather than the source, so a claim cannot pass by being
written differently than it renders. It deploys from Vercel's Git integration; the
Actions-based path is documented and deferred. - Both Play uploads moved up one track (#320): internal→alpha, alpha→beta.
- A keystore-less clone can build a release, with an F-Droid guide (#309).
- Website copy drafts are kept out of the repo (#312).
🔧 Internal
- R8 runs on every PR (#338), so the first minified build is not the one that ships.
- The Tier-0/1 deferred-work batch (#290) closed ten tracked items at once (GH#5, #11, #14,
#16, #17, #18, #20, #21, #22, #28), with 209 tests and lint enforced. - Named dispatchers are injected rather than hardcoded (#350), so the classes that use them
are testable. - Four build warnings fixed without deleting the runtime guards they pointed at (#354) —
the elvis branches survive R8 into the shipped DEX, so removing them would have swapped a
guard for a crash on non-compliant ROMs. - Behaviour tests for app-list load/refresh timing, and the seam they required (#296).
:vm-runtimealigned to Java 21 so the IDE stops rewriting.idea(#307).- An upstream AGP alpha release no longer fails every open PR (#298); both open gem advisories
closed and bundler tracked by Dependabot (#291). - Documentation corrections that contradicted the shipped
-kfreeze behaviour (#315, #318),
the follow-up index resweep (#336), the DataStore write path filed for later (#341), and the
billing reconnection backstop named accurately (#349). - Cold-start measurement work concluding that the slow
root=mode is CPU contention, not
emulation (#305, #306). - Dependency and action bumps: #275, #297, #304, #317, #324, #325.
🛠 Commits Log (v1.93.0...v1.94.0)
59236b5e— #357 adaptive home grid, and hiding the Installer and Extensions tilesfbf2d54e— #356 Ko-fi and Buy Me a Coffee in the app0ea6f93b— #354 lint warning sweep, guards preserved1f31a36f— #352 release 1.93.3172583f7— #351 probe support tiers instead of hardcoding them2f46f7e5— #350 inject named dispatchersad1a9e65— #349 name the real billing reconnection backstop585e82f3— #348 clear-data waits for the observerd7eec303— #347 judge privileged actions by a readback53a8631a— #346 name the user on every privileged command998a6c99— #345 language picker below API 339d5b677f— #343 confirmed identity and installed bytes as one set0a8b5971— #342 a resume rebuilds the billing connection3031498f— #341 file the DataStore write path535311a9— #340 app lock cold start, Recents leakcb177c78— #339 unreadable settings file no longer crash-loopsec2066bd— #338 R8 on every PR7adbc2fb— #336 follow-ups resweepe1ab6323— #334 release 1.93.238f6effa— #333 Dhizuku user id, re-init, Refreshc997124e— #332 freeze preinstalled apps by disabling (Dhizuku)a78b2255— #331 bulk removal reports what it checked9f487d3a— #330 unsuspend reports honestly2aaa45f3— #329 app list survives revoked package visibility8035db3e— #328 screenshot gate confirmed enforced17008c7c— #327 let Vercel's Git integration deploy61ca876b— #325 CodeQL action bumpff7681e3— #324@astrojs/compilerbump7dca86f3— #322 publish from master, stage from dev23bad00a— #321 site deploy from Actions7adb5955— #320 Play uploads up one track1f418e95— #319 the Thor landing page015f0831— #318 correct two-kpermission-grant claims7bd109e9— #317actions/setup-javabump99eb889b— #315 correct comments contradicting the-kfallbackd8137191— #314 freeze system apps by disabling, not uninstallingbfa6df75— #313 landing page design specddebf315— #312 keep website copy drafts out of the repo4b197ced— #311 GitHub Sponsors in-appdaaea68e— #309 F-Droid preparation, keystore-less release buildc8c0502a— #308 watchlist removal always restores8c34824e— #307:vm-runtimeJVM target 21ded1039a— #306 cold-start: contention, not emulation1b60fbeb— #305 cold-start config 1 measured on v1.93.1ec49853e— #304 Dependabot: GitHub Actionsdea40e45— #302 correct three factual errors in the v1.93.1 notesb1f270e8— #301 release 1.93.15eb19981— #300 shortcut lookup flags for a frozen system app1f35400b— #299 watchlist prompt flag off the backup55ea76b3— #298 AGP alpha no longer fails every open PR0b047fcd— #297 maven group bumpd5bc0596— #296 app-list behaviour tests and their seam2a503959— #295 named freeze profiles4e35e0b4— #294 filter the app list by permissione7748643— #293.xapkexport and bulk app backup8680325b— #292 app lock escape hatch1f99692a— #291 gem advisories, bundler tracking3d0c70a1— #290 Tier-0/1 deferred-work batchcf58be11— #289 re-sync roadmap and project docsff81bd98— #288 unified app-info sheetbec4d5ce— #287 tile and Freeze-all respect the unsafe-app blockbfa9174f— #286 rebuild pinned shortcut icons from the runner8a36ae45— #284 QS tile from real per-app freeze state1fe56bf9— #278 pull-to-refresh settle delay5799964b— #277 stop truncatinglongVersionCode67228429— #276 downgrade detection on a knownversionCodee10aebef— #275 maven group bump
Full changelog: v1.93.0...v1.94.0