Thor v1.95.4 Release Notes
Changes since v1.95.2-dev-60,
the latest release tag including prereleases, including the release preparation and batch uninstall fallback fix.
This development release brings background task tracking, safer bulk actions, stronger restore
validation, and explicit consent for legacy APK installation and permission grants.
✨ Highlights
-
🗑️ Batch uninstall without privileges. Android now asks for confirmation for each app when privileged removal fails. Cancelling one dialog continues to the next app; Stop leaves the remaining apps untouched.
-
⏳ A Queue screen for background work. Follow accepted backup/restore, single-app export,
bulk-share preparation, and supported bulk app actions; reopen their progress and retained results. -
❄️ Bulk Freeze asks first. Keep Add to Freezer checked to track successfully frozen apps,
or uncheck it to freeze without adding entries. Bulk Suspend and Unsuspend now offer background
execution and appear in Queue. -
🕘 Readable progress, newest results first. Recent tasks from both queues share one
newest-completion-first list. Status, app counter, and queue type have separate lines, and progress
dialogs keep the originating screen visible. -
📦 Older-target APK installation with consent. Eligible Root/Shizuku installs can override
Android's low-target restriction after a warning. A default-off setting lets experienced users
remember that choice. -
🔐 Permission grants are opt-in. Privileged installation no longer requests all runtime
permissions by default; the install screen and settings make the choice explicit. -
🛡️ Stronger restore validation and clearer settings. Authenticated archives, verified APK
sets, fully wrapping Freezer/security descriptions, and a Home/Settings startup crash correction. -
⚠️ App-data backup compatibility changes. Older schema-1
.thorbakbackups, including those
made by v1.95.2, cannot be restored in this version. Make fresh backups while the original app data
is still available.
What's Changed
⏳ Background tasks and the Guardians Queue — #453
Accepted work is recorded in a durable database before its foreground service is awakened. Data
work and privilege sweeps have separate queues; each processes its own work serially. The supported
paths include archive backup/restore, single-app export, bulk-share preparation, freeze/unfreeze,
per-app cache clearing, and verified Fix Store/reinstall work.
The Guardians Queue shows Running, Queued, and Recent tasks, with operation icons and
Root/Shizuku/Dhizuku identities. These identities describe the task's selected mode; they are not a
live availability check for that provider.
- Reopen a task's logger to see localized progress, per-app outcomes, pending counts, cancellation,
and recovery explanations. - Run in background, Back, or dismissing progress closes the presentation without cancelling
accepted work. Cancellation remains an explicit, request-specific action with a visible
Stopping state. - Service-start failures, unavailable observations, and interrupted work with uncertain outcomes
remain visible instead of being reported as successful. - Notifications and task details use durable task identities so stale callbacks cannot claim a new
task's work or results.
This is bounded task history, not a comprehensive action audit journal. Persisting a request does
not promise automatic completion after every reboot, force-stop, provider failure, or lost
authorization. Direct paths such as single-app quick sharing and multi-app Backup/export remain
outside this queue migration.
Implementation landmarks: 6462fe90, 77b93451, 72fa4cf7, 0fc62497, 81e7e73f,
baac6038, 1fe31820, cc67ad66, 45bf8b24, 225fce86.
❄️ Bulk-action consent and progress polish — #465
App-list Freeze now opens a confirmation dialog with Add to Freezer checked for each new
selection. Cancel or dismiss submits nothing; rotation preserves an explicit opt-out. Empty or
safety-blocked selections do not become actionable work.
The choice travels with the durable task. Checked requests add membership after a successful freeze,
including an already-frozen package; failed, busy, or absent targets are not added. Unchecked requests
do not add or remove membership. Existing watchlist entries and profile associations are preserved.
Bulk Suspend and Unsuspend are distinct service-backed operations with accurate Queue and
notification labels. Suspension readback distinguishes a suspended package from a disabled one;
single-app operations and existing Freezer/profile defaults are unchanged.
The privilege-queue notification channel now lists suspend and unsuspend alongside the existing
operations in all eight locales.
Task details now use a Navigation 3 dialog scene so the app list or other originating screen remains
behind progress. Recent tasks are sorted across both queues by completion time descending, then
sequence descending and UUID for stable ties. Pending tasks retain their existing FIFO order.
Status, counter, and queue type wrap independently rather than squeezing Completed with issues
beside the app count.
Room schema 9 → 10 adds the tracking choice with an opt-out default for older tasks. Recovery
can retry an unfinished membership write without silently changing the original request's intent.
Implementation: 9ed5a4d8; notification-description follow-up: a629b0ae.
📦 Legacy APK consent and safer permission grants — #464, #446, #451
The installer reads the target SDK from the selected APK or bundle's staged base APK. Android 14
normally blocks new installs targeting below API 23; Android 15/16 normally block targets below API
24. This concerns the APK's target SDK, not its minimum supported Android version.
For eligible Root/Shizuku shell installs, Install this time confirms the low-target bypass.
Settings → Installing → Allow legacy APK installs without asking is off by default; enabling
it skips the extra confirmation but keeps the inline warning. A one-time confirmation never enables
the setting. Cancel, changed selections, stale callbacks, and unreadable preferences do not authorize
another install.
Normal, Dhizuku, and external installation cannot apply this override. It does not bypass signature,
ABI, split consistency, downgrade, or Xiaomi system-app update restrictions, and does not guarantee
that an older app will run. Background restore/reinstall callers do not inherit this interactive
consent. The legacy-APK choice is separate from granting runtime permissions.
Install-time runtime-permission grants are now opt-in, with a per-install choice that resets for a
newly parsed package. The saved setting explains where that choice applies. Shizuku/Dhizuku broker
authorization permissions are excluded from Thor's self-grant path: broker consent must still come
from the broker. Older apps can retain their platform-defined legacy permission behavior even when
Thor does not request an explicit grant.
Implementation: 9e8611d2, 4fc55d3f, 35c21e2a, 5c0f63ff, 446c7fbb, 634da1ba.
🛡️ Authenticated restores and coordinated root work — #453, #461
Backup/restore validation now authenticates the schema-v2 manifest and archive member bytes.
Encrypted-member authentication binds the expected data class and member/chunk identity.
Unauthenticated restores are refused, rather than silently retried through a weaker path.
Backup compatibility: .thorbak app-data backups created by v1.95.2 and earlier use schema 1
and cannot be restored in v1.95.4. Create fresh backups with this version while the original app
data is still available; existing archives are not automatically converted. This format change
applies to app-data backups; ordinary APK/APKS/XAPK installation remains supported.
Before installation, staged APK sets are checked for the expected package, version, exact signer,
and valid install set. If a newly installed package becomes unsafe to continue restoring, rollback
is limited to the still-matching installed copy that Thor recorded. Unknown or changed package
identity refuses automatic deletion; this is not a blanket rollback of every restore failure.
Interactive, archive, and sweep root work now use explicit execution lanes with per-package
mutation coordination. Dedicated archive/sweep shells can fall back to serialized MainShell work
with degraded execution recorded. Cancellation is distinguished from timeout, and cancelled
interactive commands are drained before the shell is reused. Root availability checks are serialized.
The Root sweep shell now requests the global mount namespace so cache operations can see other
packages' app data. Interactive shell configuration is preserved.
Implementation: db8184f7, 2c1330a8, 3d7f9c63, 26fe6af0, ce2b9fa0,
13d50cc7, 7969ea37, 385b9bf5, 41830645, 591c96ec, 37831981.
📤 Export recovery and prepared sharing — #453, #461
Public exports reconcile recorded output identities and publish verified-name MediaStore/SAF
outputs. Recovery can recognize a completed publication instead of blindly publishing again.
Cleanup and cancellation are tied to the owning task; arbitrary provider behavior is not a
guarantee of power-loss durability or duplicate-free recovery.
Bulk sharing prepares private, task-owned files in the data queue. Reopening Share validates
ownership, expiry, file readability/size, and provider URIs before granting read-only access.
Prepared files are available for 24 hours, with best-effort expiry cleanup. Automatic format
selection uses APK for monolithic apps and APKS for split apps, not XAPK.
Reopening Export after returning from task details now resets stale Exported presentation.
Reopening while an export is active reconnects to that task without submitting another one.
Implementation: 8c49785f, b75964fc, 8efa11a7, ba9f8281, fd8b97c9, 1f6e8a7a.
⚙️ Readable settings and startup compatibility — #464
All four Freezer switches and the biometric-lock explanation use fully wrapping titles and
descriptions, preserving one accessible switch target. Larger text can expand the scrolling
category without cutting off the explanation. The legacy-install setting uses the same readable
treatment.
Biometric-lock copy now explains launch authentication, screenshot/recording protection, and the
hidden Recents preview across all eight app locales. This is a discoverability change:
existing app-lock and screenshot-protection behavior is unchanged.
A Material 3 API removal had made Asgard's Home/Settings controls throw NoSuchMethodError at
startup. Thor restores the compatible alpha26 runtime with a strict constraint and excludes the
known-incompatible alpha27 Dependabot update. Real-Asgard control tests cover that failure; the
separate Asgard library upgrade is not part of this release.
Implementation: 99ddd6ac, 634da1ba.
🧰 Project & Internal
- Database and recovery coverage — exported schemas, migration tests, claim ownership,
cancellation/recovery fencing, legacy-work compatibility, notification identity, publication
ownership, and UI lifetime regressions accompany the foreground-service migration
(#453,
#461,
#465). Historical intermediate WorkManager
commits appear in the log; the final supported queued paths use the foreground services. - Website maintenance — Astro, MDX, sitemap, Node typings, and lockfile dependency updates,
including fast-uri, SVGO, and js-yaml maintenance. The web-only updates merged on master are
reflected in dev via #463; related grouped
updates are #448 and
#456. The internal worker-lane page is excluded
from indexing. This does not claim a fix for the separate, unreproduced Android Brave layout report. - Build and workflow upkeep — AGP 9.5.0-alpha04, Coil 3.6.2, test infrastructure, CodeQL,
setup-java, and release-action updates
(#447,
#449,
#450,
#455,
#457,
#459). The Material 3 compatibility constraint
above deliberately takes precedence over the incompatible update. - Community assessment and acceptance records — GitHub, Telegram, and Reddit requests are
reconciled into implementation, product-decision, extension, and deferred work. The assessment
itself is not implementation of every request. Device reports are distinguished from automated
checks and remaining mode/edge-case acceptance. - Release bookkeeping — version code 1954, derived name 1.95.4. Code 1953 was already
consumed for Play's special-use foreground-service access submission and is deliberately skipped. Play
notes are mirrored to every Fastlane locale; release-note retention remains 20 directories.
The Shizu manifest remains tied to the latest production release, not this development release.
🧪 Verification and Remaining Acceptance
Release-preparation gates passed for version code 1954 on JDK 21 with a single worker and
a 4 GiB Kotlin compiler heap. These invocation options leave the project build settings unchanged:
./gradlew test lintFossDebug lintStoreRelease \
assembleFossDebug compileFossDebugAndroidTestKotlin \
--console=plain --no-parallel --max-workers=1 \
'-Pkotlin.daemon.jvmargs=-Xmx4g -XX:+UseG1GC' \
--no-configuration-cache --no-build-cache \
-Pkotlin.incremental=false -Pksp.incremental=false- FOSS Debug and Store Debug each passed 2,763 tests across 232 suites, with no failures,
errors, or skips. The full suites ran after the notification-description updates; the final
pre-PR gate reused those unchanged unit-test outputs. - Both lint gates have zero errors and no
MissingTranslationfindings or:bypass
SyntheticAccessorerrors. Existing warnings/hints remain: FOSS 66/15, Store 53/15. - Release-note budgets pass: Play 487/500 characters and Telegram 964/1024 assembled UTF-16
units. Both Fastlane locale copies match the Play source byte-for-byte; locale parity and the
production-pinned Shizu manifest check pass. The shell suite passed before and after retention
pruning (10 test files); Fastlane's Ruby suite passed 35 tests / 49 assertions. The retained
20 releases have no broken references to the removed v1.90.3 notes, and historical Fastlane
changelogs remain intact. - FOSS debug assembly and Android-test Kotlin compilation passed in the final pre-PR gate.
The earlier bulk-action checkpoint passed 23 disposable-emulator instrumentation tests,
covering migrations and task dialogs. These were not rerun for the presentation-only Recents
polish or this release preparation. - Queue ordering, restoration, callbacks, and separated labels are tested across eight locales,
including RTL, at 320dp/2× and 600dp/1.5× text. - The maintainer reports physical-device confirmation of legacy APK installation through Root,
settings readability, basic bulk Freeze consent/tracking choice, service-backed Suspend/Unsuspend,
correct dialogs, and the latest Queue ordering/layout. These reports do not identify a complete
device/ROM/privilege-mode matrix. - Earlier service-queue acceptance records contain physical-device and emulator checks, but
T19 remains deferred and T22 remains incomplete. No universal recovery, matched performance
improvement, or exhaustive Root/Shizuku/Dhizuku acceptance is claimed. Legacy-install OEM/mode
coverage, archive/provider edge cases, and relevant Store-policy checks remain documented follow-ups.
🛠 Commits Log
Complete non-merge history in chronological order: 130 commits from
v1.95.2-dev-60..a629b0ae. This includes documentation, tests, maintenance, and intermediate
implementation stages; the final behavior is described above.
9e8611d2fix(installer): make the install-time permission grant opt-in4fc55d3ffeat(installer): let the user answer the permission grant per install35c21e2ai18n(settings): say which installs the permission grant reaches5c0f63fffix(installer): clear the per-install grant answer when a new package is parsed092050c9docs: record worker shell lane design6b150603fix(freezer): terminate bulk progress on cancellation7333706dtest(freezer): assert outer cancellation job7425be97chore(deps): bump the maven group with 3 updatesbf6b12b5feat(privilege): define execution lane contractsdb8184f7feat(privilege): coordinate package mutations4855a78bfix(privilege): make package lease handoff atomic2c1330a8feat(root): own isolated archive and sweep shells45c278bdfix(root): harden isolated Odin shell ownership53960f49fix(root): distinguish cancellation from command timeout3d7f9c63feat(root): route commands across three lanes41b195b2fix(root): make MainShell submission atomic49d7c8f2fix(root): handle MainShell acquisition failuref82507aerefactor(root): centralize privileged command routing042849barefactor(root): centralize privileged command routing93649881refactor(root): centralize privileged command routinga4ae5e37refactor(root): centralize privileged command routing4d7a6381test(root): cover concrete execution boundaries428d4e4dfix(archive): isolate root commands from interactive work97aa3817fix(archive): preserve force-stop execution metadatab30d7e64chore(deps): bump the web group in /web with 3 updates152e4618chore(deps): bump the actions group with 2 updates9aa77b03feat(sweep): persist durable request snapshots2ae1df2bfeat(sweep): enqueue and observe durable requests8e01e7d5fix(sweep): expose durable request discoverye44b1e94chore(deps): bump the maven group with 5 updates6b56bf74fix(backup): preserve enqueue handoff before background7cd21db0fix(archive): access private data through archive shellf55a4981fix(sweep): preserve enqueue and source observationbe5fb3a2feat(sweep): cancel the durable queue safelyd588143etest(room): enable migration foreign keys756700c6feat(sweep): execute durable privilege sweeps446c7fbbfix(privilege): exclude broker authorization self-grants61c8a888feat(sweep): migrate bulk freeze actions to WorkManager890bbf31fix(sweep): retain durable profile identityeca4eb74feat(sweep): render durable progress statesd6717b8bfix(sweep): prefer newest retained profile request0844212dfeat(sweep): add replay-safe cache and reinstall actions34f67e1efix(sweep): reject failed reinstall path lookups5479b643fix(deps): restore Asgard Material3 compatibility9ddcaa94docs(workers): document shell lanes and sweep statesce7e5895test(sweep): avoid restricted WorkManager states2fa5b714test(sweep): align dialog lifecycle assertions26fe6af0feat(archive): authenticate schema v2 manifestsce2b9fa0feat(archive): authenticate bundle bytes13d50cc7feat(archive): verify staged APK clusters7969ea37feat(archive): roll back unsafe new installs385b9bf5feat(archive): refuse unauthenticated restores7e07a2e9fix(archive): harden authenticated restore flow9671462cfix(archive): close install cancellation gapd516a54ddocs: record worker lane acceptance evidence41830645fix(root): drain cancelled interactive MainShell work23c4677cdocs: finalize worker lane acceptance evidence60dc074efix(web): exclude internal lane architecture page591c96ecfix(privilege): serialize root availability probesc1c4fe29docs(workers): capture service queue latency baselinec08bc4c4fix(ui): remove release latency draw hooksa70daf65feat(queue): define durable task contracts5ab628bffix(queue): validate durable task contractsc057ceeafix(queue): harden presentation argumentsc147f04dfeat(database): add complete durable queue schemae18762c8fix(database): harden durable task recovery0a8fbf8efix(database): require explicit recovery livenessc7f37826test(database): prove final drain writer contention148265defeat(database): add sweep target claim transactions1d70dd69fix(database): harden sweep claim recoverye1902e28fix(database): reject partial sweep ownershipb84dabe3fix(database): fence malformed sweep targets3fddfa47fix(database): validate sweep ownership tokens9e662f48fix(database): align sweep runnable detection6462fe90feat(queue): add durable data task acceptance5667f97cfix(queue): preserve keys after accepted cancellation0a8f5422refactor(backup): extract archive task runners5cee7b2fchore(deps): bump io.coil-kt.coil3:coil-compose in the maven group7e4ea876fix(backup): harden archive task recovery70be7af0refactor(export): add resumable data task runners8c49785ffix(export): reconcile durable publicationsb75964fcfix(export): fence durable publication leases33265150feat(service): add typed foreground prerequisites77b93451feat(service): activate durable data queuee8e7d98efix(service): harden data queue ownershipa4bd12dafix(service): fence data queue generationsf4f5a2bdfix(service): complete data queue fencing76f87280fix(service): close data queue recovery gaps67672a0dfix(service): settle exact data claims9be7d51afix(service): finish recovered data claimse76894e8fix(service): close data recovery racesb6efdb3cfix(service): preserve data recovery completiona0afd1abfix(service): retry inherited data recoveryb088330fchore(deps): bump the web group in /web with 4 updates17c75f05chore(deps): bump softprops/action-gh-release in the actions groupd535d3f5fix(service): gate inherited recovery retry3b7ca931feat(sweep): recover durable target state9a108824fix(sweep): preserve legacy work identity72fa4cf7feat(sweep): activate foreground service queuee5b80b5brefactor(workers): isolate compatibility execution0fc62497feat(queue): project durable task history81e7e73ffeat(queue): add task queue screenbaac6038feat(queue): add task detail logger1fe31820feat(queue): navigate to durable task details8efa11a7feat(share): queue durable share preparation and retentionba9f8281fix(share): reclaim cancelled and recovered share outputscc67ad66fix(queue): render localized progress and cancellation notificationsc8da3fb3chore(build): retain Studio AGP alpha04 update92e7d476docs(queue): record service migration and remaining acceptance gatesb4d3328bdocs(queue): record PR publication and final review gate93439274fix(test): avoid recursive source-sanitizer overflowd1eda765chore(web): update fast-uri to patched 3.1.798be2f6cdocs(queue): record CI corrections and review recoveryfd8b97c9fix(queue): harden task ownership, export publication and UI lifetimes25ffb74edocs(queue): record CI acceptance and bounded emulator measurements45bf8b24fix(queue): consolidate pending logs and space task controls225fce86feat(queue): add Guardians branding and operation iconseada3844chore(deps): bump the maven group across 1 directory with 2 updates1f6e8a7afix(export): reset sheet state after task navigation37831981fix(root): use mount-master for sweep cache operationscb1068d5chore(queue): remove acceptance probes and stabilize Back test8bc360d6docs(queue): reconcile physical and emulator validation9e65c745chore(deps): bump svgod3e8d418chore(web): sync Astro and js-yaml updates from master99ddd6acfix(deps): pin Material3 to the Asgard-compatible runtime634da1bafeat(installer): add legacy APK consent and readable settings95ba6770docs(community): record feature assessment and validation statusd91378d3docs(installer): clarify disposable smoke-test emulator9ed5a4d8feat(app-list): confirm bulk actions and improve task progressa629b0aefix(i18n): describe queued suspend and unsuspend actions
Integration commits
All 20 merge commits in the same range, also in chronological order.
72c25630Merge pull request #446 from trinadhthatakula/fix/install-grant-all-permissions-optout089de141Merge pull request #447 from trinadhthatakula/dependabot/gradle/dev/maven-3bed97aa0c2cc58c2dMerge pull request #449 from trinadhthatakula/dependabot/github_actions/dev/actions-38908e43030c756735Merge pull request #448 from trinadhthatakula/dependabot/npm_and_yarn/web/dev/web-3a2be4fbadd3e31c0bchore: sync feat/worker-shell-lanes with devb40334d1Merge pull request #450 from trinadhthatakula/dependabot/gradle/dev/maven-424c98aa43ed247f21Merge remote-tracking branch 'origin/dev' into feat/worker-shell-lanesa4b1fcefMerge pull request #451 from trinadhthatakula/fix/shizuku-self-permission-grant63d1d39bMerge remote-tracking branch 'origin/dev' into feat/worker-shell-lanesceb201ddMerge pull request #455 from trinadhthatakula/dependabot/gradle/dev/maven-3410fb38ec9765d974Merge pull request #457 from trinadhthatakula/dependabot/github_actions/dev/actions-6512b1d693deaf370aMerge pull request #456 from trinadhthatakula/dependabot/npm_and_yarn/web/dev/web-8b5dee0527a3fa6321Merge remote-tracking branch 'origin/dev' into feat/worker-shell-laneseb2345e1Merge pull request #453 from trinadhthatakula/feat/worker-shell-lanesd091ecb7Merge pull request #459 from trinadhthatakula/dependabot/gradle/dev/maven-fe4467dd0002b0956eMerge dev into chore/service-queue-emulator-acceptance504e3418Merge pull request #461 from trinadhthatakula/chore/service-queue-emulator-acceptance5c9672e6Merge pull request #463 from trinadhthatakula/chore/sync-web-dependabot-to-dev50dd13c3Merge pull request #464 from trinadhthatakula/feat/legacy-apk-install3f5ec724Merge pull request #465 from trinadhthatakula/feat/legacy-apk-install