Skip to content

feat: Various misc QoL enchancements#3398

Open
secp192k1 wants to merge 28 commits into
devfrom
feat/fate-weir
Open

feat: Various misc QoL enchancements#3398
secp192k1 wants to merge 28 commits into
devfrom
feat/fate-weir

Conversation

@secp192k1

@secp192k1 secp192k1 commented Jun 4, 2026

Copy link
Copy Markdown
Member

What was added

Live updates for patcher screen

Implemented live updates for: Decoding resources
  • Ticks Decoding resources [space] Xs) every second, replacing in place
  • Final Decoded resources in Xs replaces the live tick
  • Final summary also written to the exported log file
Live updates: In progress Live updates: Finished Logs snippet
image image [INFO] Applying patches
[INFO] Decoded resources in 31s
[INFO] Writing patched files...
Implemented live updates for: Downloading APK file
Live updates: In progress Live updates: Finished Logs snippet
image image [INFO] Applying patches
[INFO] Decoded resources in 31s
[INFO] Writing patched files...

CI: Skip automatic builds for drafts

As the title implies, it just prevents GitHub Actions to build on PR drafts, everything else it unchanged (i.e. keeps building on commits)

Clear downloaded APK cache

Just a button under Settings => Developer options to clear the cache of downloaded APKs

Image preview image

Fix network memory leak

The flow for donwloads is:

  1. Read chunk
  2. Write chunk
  3. Repeat

Every read operation allocates a small byte container, everything writes correctly, but the containers are never freed from memory after use, causing them to persist and accumulate

To fix this, we wrap each chunk inside a .use { } blocks for automatic resource management:

  • Read chunks: containers are automatically freed from memory once processed
  • Output sink: ensure it is properly flushed and closed, preventing the memory leak

Instead of wrapping the main logic in an if (success) check, it now throws early if it fails
It keeps the exact same behavior but saves us a level of indentation and its better practice

Fixes #3392

Dedicated dialogs for patcher kills and native crashes

ProcessRuntime throws a generic Exception for any non-zero exit code from the patcher subprocess, leaving users with the generic "Process exited with nonzero exit code X"
A good example is on #3060 which on low-RAM devices is almost always Android's LowMemoryKiller, not a real bug

Added signal exit codes to two typed exceptions routed to dedicated InstallerStatusDialog kinds:

  • PatcherKilledException for when the system terminates the process
  • PatcherCrashedException for when the process crashed natively

And InstallerModel now has a retry() so the dialog button can a clean install attempt can be tried from the installer screen

Soft-skip failed patches - WIP!

When a patch fails, the entire process dies, this addition makes it so keeps patching, skipping the failed patch

Image preview
image image

Fix step icons not aligned

Image preview
Before After
image image

Removed useless prefix

The prefix [INFO] is useless and shouldn't be there, removed only for the INFO level

Styling and refactor

`strings.xml` has style tags like `` but it doesn't work, now it does!
Screenshot_20260606_175300 Screenshot_20260606_210058

Now these components animate:

  • Notifications in the dashboard
  • Battery optimization warning
  • Onboarding permission items

@secp192k1 secp192k1 added the Feature request Requesting a new feature that's not implemented yet label Jun 4, 2026
@oSumAtrIX

oSumAtrIX commented Jun 4, 2026

Copy link
Copy Markdown
Member

Maybe just say "Decoding resources (Xs)" instead of "Still". However this entire PR has a contract issue.

The patcher is what emits the arbitrary log strings. There is no contracted guarantee those strings will change. You cant "assume" a specific string

Similarly replaceLineAtIndex isnt guaranteed, since the patcher might emit more or less strings shifting the index.

@Ushie

Ushie commented Jun 8, 2026

Copy link
Copy Markdown
Member

What's left here? it's best to make sure this doesn't remain in this PR for too long otherwise it's going to stale out like its siblings

@secp192k1 secp192k1 marked this pull request as ready for review June 8, 2026 16:55
@secp192k1

Copy link
Copy Markdown
Member Author

What's left here? it's best to make sure this doesn't remain in this PR for too long otherwise it's going to stale out like its siblings

Only thing that was left was any future ideas of what to add next, but I 100% agree. Readyyyyy

@PalmDevs PalmDevs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs some small changes.

Will string changes be reflected to Crowdin? @validcube

Comment thread app/src/main/res/values/strings.xml Outdated
Comment thread app/src/main/res/values/strings.xml Outdated
Comment thread app/src/main/res/values/strings.xml Outdated
<string name="patches_force_download">Force download all patches</string>
<string name="patches_reset">Reset patches</string>
<string name="downloaders_reset">Reset downloaders</string>
<string name="downloaded_apps_clear">Clear downloaded APK cache</string>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is necessarily always an APK. In the future, I think we may support other formats, so it's best to do

Clear download cache or Clear downloaded apps cache.

Thoughts @oSumAtrIX?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, regardless, if thats the case for the future, maybe Clear downloaded assets cache

} No newline at end of file
}

private val boldRegex = Regex("</?b>")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this how other apps do it? Is there a dependency that does this for us?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure how other apps do it I'll be honest

I personally like the regex method because its the cleanest, instead of a bunch of text splits and find index etc..

@validcube

Copy link
Copy Markdown
Member

Needs some small changes.

Will string changes be reflected to Crowdin? @validcube

For changes applied to default language strings (english), yes. For changes applied to other languages they will be reverted to original state.

@secp192k1 secp192k1 requested a review from PalmDevs June 12, 2026 15:41
@oSumAtrIX

Copy link
Copy Markdown
Member

I have asked this before but still have not understood how you are certain about where to inject "[INFO] Decoded resources in 31s" in the logs. What if the patcher adds or removes certain logs? How do you know when decoding starts and ends? This part of the process is not in the hands of manager, and probably even a concern of patcher if at all.

Soft-skip failed patches - WIP!

Patches should never raise an exception back to caller. Can you provide a stack trace or location where an exception is unhandled?

The prefix [INFO] is useless and shouldn't be there, removed only for the INFO level

The formatting is not user friendly. Instead it should say:

Info:
Error:
Warning:

or co.

Perhaps the raw log string can also be styled, but a "lazy" string works too for the while.

@secp192k1

Copy link
Copy Markdown
Member Author

What if the patcher adds or removes certain logs?

We never read the patcher's logs to time this. The timer is just a coroutine ticking every second, totally separate from whatever the patcher prints:

val heartbeat = Heartbeat(this) { elapsed ->
    emitLog("Decoding resources", "${elapsed}s")
}

The patcher could even not print anything and it would still keep ticking

How do you know when decoding starts and ends?

The start time will be the moment patcher is called, and the end time is the first patch result we get back, the first step is to decode the resources which makes it predictable

patcher { (patch, exception) ->
    heartbeat.complete { elapsed ->
        val message = "Decoded resources in ${elapsed}s"
        logger.info(message)
        emitLog(message)
    }
    // ...

And we cant call complete multiple times because of this line (fun complete() at Heartbeat.kt)

if (!finished.compareAndSet(false, true)) return

So later patches dont run this again

This part of the process is not in the hands of manager, and probably even a concern of patcher if at all.

Yes and that stays true since we arent changing any behaviour of the patcher, just using its steps to get a rough estimate of the ETA

Patches should never raise an exception back to caller. Can you provide a stack trace or location where an exception is unhandled?

And it doesnt, the exception gets passed as a regular string variable on the result callback, nothing is unhandled

patcher { (patch, exception) ->

local val patch: Patch
local val exception: PatchException?

The only place an exception actually flies is at the very end of that callback, where we re-throw it on purpose to abort the run

if (exception != null) {
    // ...
    phaseLogger.error("${patch.name} failed:")
    phaseLogger.error(exception.stackTraceToString())
    throw exception
} // ...

The soft-skip is only preventing the re-throw when the exception looks like a missing fingerprint, and we add a return@patcher to keep the loop going

Ideally, re-throwing would never happen and for each failed patch its treated as a soft-skip / skippable task
After all, it is a WIP, I wasn't sure if some failures leave the APK half-broken since some patches depend on others, making it unsafe to just completely ignore..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature request Requesting a new feature that's not implemented yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: unable to download patches; Out of Memory feat: provide "proof of life" when downloading apk bundles

5 participants