Skip to content

Background crawls stall: no foreground service or wakelock #74

Description

@xroche

Switching away from the app during a mirror makes the crawl appear to pause. It's not the app pausing it — onPause() sets a paused flag (HTTrackActivity.java:3053) that is never read, so nothing in the crawl path acts on it. The crawl runs as a plain background AsyncTask thread (engine.main(), HTTrackActivity.java:1302) with no foreground service and no wakelock anywhere in app/src/main/.

So once backgrounded the crawl is at the mercy of Android background management — CPU/network throttling, Doze/App Standby with the screen off, and process freeze/kill under memory pressure. Progress stalls and resumes on return to foreground, worst with the screen off.

The code already flags this as known (HTTrackActivity.java:2356-2358): reliability while backgrounded "relies on us staying the MRU cached process. A foreground service (needs an Android-14 FGS type) is a future step."

Fix: run the crawl under a foreground service with an Android-14 FGS type (dataSync) and a progress notification, plus a partial wakelock held for the duration of the crawl. Dead-code cleanup of the unused paused flag can ride along.


Cost note (2026-08-03). The code is the cheap part. A keep-alive foreground service that holds the process foreground while the existing AsyncTask runs unchanged is about 200 lines: the service itself, three manifest permissions plus a <service> entry, start/stop calls at startRunner() and on the stopMirror/onPostExecute paths, and deleting the dead paused flag.

What costs is everything outside the diff. Declaring dataSync obliges us to fill Play's foreground-service declaration form, a description plus a demo video, which Google reviews; a rejection blocks the whole release, not just this feature. That is the dominant cost and the reason to think twice. Then targetSdk is 35, where dataSync is capped at 6h per 24h, after which the system calls Service.onTimeout() and ANRs us if we don't stop. Long mirrors will hit that, so we also need a degradation path: drop the FGS, notify, let the crawl carry on unprotected. And there is no unit or instrumentation suite here, so verifying any of it means a device, a multi-hour crawl with the screen off, and forced Doze via adb shell dumpsys deviceidle force-idle.

Worth recording that this does not survive a swipe from recents either: RunnerFragment.onDestroy() stops the mirror. Fixing that means moving the crawl into the service and untangling Runner from the activity, which is multi-session work with nothing to catch a regression.

Deferring until the next release at the earliest.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions