Skip to content

feat: explain every killing app option and add advanced pause - #498

Open
FairyJJ wants to merge 3 commits into
XayahSuSuSu:mainfrom
FairyJJ:feat/advanced-pause-kill-option-docs
Open

FairyJJ wants to merge 3 commits into
XayahSuSuSu:mainfrom
FairyJJ:feat/advanced-pause-kill-option-docs

Conversation

@FairyJJ

@FairyJJ FairyJJ commented Oct 25, 2026 •

Copy link
Copy Markdown

What

The Killing app options list only offered "Option One" / "Option II" with no hint about what they actually do, and none of the options keeps an app from being restarted while its data is being archived.

1. Explain every option

  • The selection dialog of Backup settings and Restore settings now shows how each option is implemented (kill_app_option_disabled_desc, kill_app_option_i_desc, kill_app_option_ii_desc, kill_app_option_iii_desc).
  • The explanation of the selected option is also shown under the settings row, so the current behaviour is visible without opening the dialog.
  • Existing string keys are untouched, so the current translations keep working. The new strings are translated to zh-rCN, zh-rTW and zh-rHK and are ready for Weblate.

2. New option: Advanced pause (OPTION_III)

  • am force-stop --user <id> <pkg> followed by pm suspend --user <id> <pkg>, executed through the root shell.
  • A suspended package cannot be started by the launcher, broadcast receivers, services, jobs or other apps, and its launcher icon is greyed out, so it cannot repopulate its files while they are being archived.
  • releaseApp() lifts the suspension in a finally block right after the package has been processed (backup and restore), so it only lasts for one app instead of the whole task.
  • SuspendUtil writes every suspended package to files/suspended_packages.txt before suspending, and releaseApps() unsuspends all leftovers when the next task starts, so a killed task cannot keep an app suspended forever.
  • System packages (PackageEntity.isSystemApp) are only force-stopped, never suspended, because suspending systemui or the launcher can take the device down. The app never suspends itself either.

3. Opening the app during a backup no longer fails it

The goal is that both statements hold at the same time: the app cannot be opened while it is being backed up, and trying to open it does not change the outcome of the backup. Three things were missing for the second half.

  • tar exit code 1 is accepted (PackagesBackupUtil.isAcceptableTarResult()). tar exits with 1 when a file changed or disappeared while it was being read – exactly what a launch attempt causes – and the archive is still readable. Exit code 1 therefore no longer fails the package; testArchive() stays the verdict on the archive. Exit code 2, a timeout and a missing result still fail.
  • reinforcePause() re-applies the suspension between the passes of a backup (after the APK and the device-protected data) and before the data pass of a restore, so a package that got started in between is stopped again before its data is read.
  • Every pause helper is best-effort. killApp(), releaseApp(), releaseApps(), SuspendUtil and BaseUtil.suspendPackage() / unsuspendPackage() only log a failure; a failed kill or a failed suspend can no longer abort the task. SuspendUtil also runs on Dispatchers.IO + NonCancellable so a cancelled task still lifts every suspension it applied, and resumePackage() always unsuspends instead of trusting a possibly stale record.

Modules

  • app (resources, ids.xml)
  • core:model, core:util, core:service
  • feature:main:settings

Test evidence

  • ./gradlew :core:service:compileDebugKotlin :feature:main:settings:compileDebugKotlin -> BUILD SUCCESSFUL
  • ./gradlew :app:processArm64-v8aFossDebugResources -> BUILD SUCCESSFUL (arrays / ids / strings / translations)
  • ./gradlew :app:assembleX86_64FossDebug :app:assembleArm64-v8aFossRelease :app:assembleArm64-v8aPremiumRelease -> BUILD SUCCESSFUL
  • Runtime behaviour of option III was measured on an Android 13 (API 33, x86_64) emulator booted with -writable-system, with a plain AOSP su replaced by a root daemon:
Scenario Opening the app Archive
Paused, 60+ am start attempts while the archive is written never started, no process created, suspended still true afterwards tar exit code 0, decompresses and lists, 4 × 50 MB payload intact
Control: not paused, one am start started, pid: 20094, app recorded the launch –
Control: paused, one am start not started, pid: (none), app recorded nothing –

Notes

  • docs/kill-app-options.md (+ docs/kill-app-options.zh-CN.md) documents all four options against the code, including the three layers above and the measurements.
  • No screenshots: the dialog reuses the existing DialogRadioItem.desc and Selectable.desc slots, so the layout is unchanged.

The killing app options were named "Option One" and "Option II" without any
hint about what they actually do, and none of them keeps an app from being
restarted while its data is being archived.

* Show how each option is implemented in the selection dialog and under the
  current selection of both the backup and the restore settings page.
* Add KillAppOption.OPTION_III (Advanced pause): force-stop the package and
  suspend it with `pm suspend`, which the platform honours for every start
  request, so the launcher, broadcasts, services and jobs cannot bring the app
  back while it is processed. Its icon is greyed out meanwhile.
* Lift the suspension in a finally block right after the package has been
  processed, and lift every leftover suspension when the next task starts, so
  an interrupted task cannot keep an app suspended forever.
* Never suspend system packages (fall back to a force-stop) and never suspend
  the app itself.
* Document all four options in docs/kill-app-options.md and its Chinese
  translation.
…acked up

Opening a paused app while it is being backed up used to fail the package,
even though the archive that was produced was perfectly readable.

- `tar` exits with 1 when a file changes or disappears while it is being read,
  which is exactly what a launch attempt does. Exit code 1 is now accepted and
  the archive itself stays the verdict (`testArchive`); exit code 2, a timeout
  and a missing result still fail.
- Kill, pause, resume and resume-all are best-effort helpers now and can no
  longer abort the task: `killApp`, `releaseApp`, `releaseApps`,
  `SuspendUtil` and `BaseUtil.suspendPackage` / `unsuspendPackage` are wrapped
  in `runCatching` and only log the failure.
- A launch attempt is re-checked before the data is read: `reinforcePause`
  re-applies the pause after the APK, split APK and device-protected data
  passes of a backup, and after the install pass of a restore, so a package
  that got started in between is stopped again before its data is archived.
- `SuspendUtil` runs on `Dispatchers.IO + NonCancellable`, so a cancelled task
  still lifts every suspension it applied, and `resumePackage` always
  unsuspends instead of trusting a possibly stale record.
Describe the three layers that make "cannot be opened" and "the attempt does
not matter" hold at the same time: the suspension itself, the accepted `tar`
exit code 1, `reinforcePause`, and the best-effort wrapping of every pause
helper. Includes the control-group measurements from an android-33 emulator.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant