Show an ongoing notification while the app is working

Everything this app asks for is minutes of the build machine's time
followed by a download, and all of it runs in the list screen's own
coroutines -- so with the app in the background the process is an
ordinary cached one, and Android is free to take it away halfway
through a build. A foreground service is the only way to say otherwise,
and its notification is both what that costs and what it is for: the
work is visible from the shade while it runs, and one tap comes back to
the card that started it.

The work itself stays where it is. WorkNoticeService runs nothing --
it collects a list of what is happening and reposts one notification,
stopping as soon as the list is empty -- and that list is *derived* by
workItems from the same projectStates/componentStates the cards are
drawn from, so there is nothing to forget to post and nothing to forget
to take back. The words are shared rather than written twice:
componentWorkLine and buildLine are now read by the card's own bars and
by the notification alike.

Measured on API 36 rather than assumed: the collapsed row drops the
content text the moment there is a progress bar, so one thing running
puts both the component and what is happening to it in the title
("Test Tablet / tablet: building  14/30"); several get a line each in
the expanded view and an indeterminate bar, there being no honest
single number for two builds at once. With the app at the home screen
the process sits at fg-service-act rather than cached, and the builds
it started landed while it was there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-19 19:36:03 -04:00
1 parent 4d00fe1b60
commit bb65526230
9 files changed
+583 -48

No files matched your search

+47
View File
@@ -845,6 +845,53 @@ mutable at runtime from the phone.
Reading the output means splitting on carriage returns as well as
newlines: a tool redrawing a counter in place puts several updates and
then real output inside one `\n`-delimited line.
- **Work in flight holds a foreground service, and the notification is
what that costs and what it is for** (`WorkNotice.kt`). Everything this
app asks for is minutes of the build machine's time followed by a
download, and all of it runs in the list screen's own coroutines -- so
with the app in the background the process is an ordinary cached one and
Android is free to take it away in the middle of a build. A foreground
service is the only way to say otherwise; measured on the emulator, the
process sits at `fg-service-act` (oom adj 50) with the app at the home
screen, where it would otherwise be cached, and the builds it started
landed while it was there.
The work itself stays in the screen. The service runs nothing: it
collects a list of what is running and reposts one notification, and
stops as soon as the list is empty. That list is **derived** by
`workItems` from the same `projectStates`/`componentStates` the cards
are drawn from, rather than posted by each action that starts something,
so there is nothing to forget to post and nothing to forget to take
back, and the shade cannot come to claim something the card does not.
The words are shared rather than written twice: `componentWorkLine` and
`buildLine` are read by the card's own bars and by the notification, for
the reason the note about two words for one measurement gives.
Three things measured on API 36 rather than assumed. **The collapsed row
drops the content text the moment there is a progress bar**, so with one
thing running the title carries both the component and what is happening
to it (`Test Tablet / tablet: building 14/30`) and nothing is set
beside it, which expanded would be the same words twice. **Several at
once get an indeterminate bar** and a line each in the expanded view:
there is no honest single number for two builds, and averaging them
draws something that moves like a measurement without being one.
**An app may only start a foreground service while it is in front of
somebody**, which is ordinarily where this one is -- work begins with a
button, and the service outlives the press; everything after the start
is an update to a `StateFlow` the service is already collecting, so a
build that finishes while the phone is in a pocket still reports. What
is left is work *reappearing* after the list has been empty for a moment
with the app by then behind something else, and Android answers that
with an exception rather than a return value -- so the start is asked
for whenever there is work and nothing showing it, the refusal is
caught, and the next thing the run reports asks again. Stops go through
`stopSelf(lastStart)` rather than `stopSelf()`, so work starting in the
moment between deciding to stop and stopping is not taken away with it.
`POST_NOTIFICATIONS` is asked for at startup beside the local-network
one, and refusing it costs the watching rather than the work: the
service still runs, and there is simply nothing to draw. The type is
`dataSync`, whose 6-hour daily budget on Android 15+ arrives as
`Service.onTimeout` and is answered by stopping -- unreachable for a
build, but a build machine that says "building" and never stops is
polled for as long as it keeps saying it.
- **Discovery must stay side-effect free.** It runs on every manifest
request and every suggestion scan.
- **An app in the list is a *project*, and what it produces is its