Skip to content

fix: recover tile state from STATE_UNAVAILABLE after upgrade or boot#221

Open
dambrisco wants to merge 5 commits into
SimonMarquis:mainfrom
dambrisco:main
Open

fix: recover tile state from STATE_UNAVAILABLE after upgrade or boot#221
dambrisco wants to merge 5 commits into
SimonMarquis:mainfrom
dambrisco:main

Conversation

@dambrisco

Copy link
Copy Markdown

Fixes issues with the Sleep Tile becoming unavailable after app updates, app stops, and device reboots.

ACTIVE_TILE made SystemUI bind the tile only when we called requestListeningState(). If that chain ever broke (broadcast not delivered, bind fails, application stopped, etc.), the tile defaulted to STATE_UNAVAILABLE. By switching it to passive, SystemUI binds it every time the panel opens and refreshTile rewrites the state on every display. A separate race set STATE_INACTIVE after a tap because notificationManager.notify() the just-posted notification wasn't yet visible to find()show()/update()/toggle() now return the posted Notification so refreshTile bypasses find() on state changes.

Confirmed that the tile survives both upgrades and reboots with these changes on my personal device using the apk built here: https://github.com/dambrisco/SleepTimer/actions/runs/25584393419

Fixes #179

dambrisco added 5 commits May 6, 2026 18:38
OS upgrades reboot the device and can leave the active tile in
STATE_UNAVAILABLE: SystemUI restarts, the app process is in stopped
state, and active tiles are not auto-bound. With no launcher activity
and click disabled in UNAVAILABLE, the tile has no recovery path.

Listening for BOOT_COMPLETED wakes the receiver after the user unlocks,
which calls requestTileUpdate() and refreshes the tile.

LOCKED_BOOT_COMPLETED is omitted intentionally: the SleepTileService is
not directBootAware, so binding it before the user unlocks would fail
anyway; BOOT_COMPLETED runs at first unlock, which is when the tile
matters.
Active tiles depend on the app calling requestListeningState() at the
right moments to keep state in sync. Recovery paths for stuck
STATE_UNAVAILABLE (MY_PACKAGE_REPLACED, BOOT_COMPLETED, onTileAdded) are
not enough on their own — users still report the tile being greyed out
after a reboot.

Removing ACTIVE_TILE makes SystemUI auto-bind the service whenever the
QS panel is shown, so onStartListening runs and refreshTile sets
STATE_INACTIVE/STATE_ACTIVE on every panel open. The tile cannot remain
stuck at STATE_UNAVAILABLE because the system itself drives the
refresh.

Existing requestTileUpdate() calls are preserved; for passive tiles they
just force an immediate listening cycle, which is still useful for
push-style updates from SleepActionReceiver and SleepAudioService.
The receiver's intent-filter, the RECEIVE_BOOT_COMPLETED permission,
and the onTileAdded override were all attempts to recover from
STATE_UNAVAILABLE under the active-tile model. The passive tile in
e429ae4 means SystemUI auto-binds and refreshes on every QS open, so
those recovery paths can't fire usefully — they're just dead code that
implies the tile cares about boot/package events when it no longer
does.

SleepActionReceiver still receives explicit action broadcasts (notification
taps, ADB) via component-targeted intents, so the receiver itself
stays.
After onClick → toggle() → show() → notify(), refreshTile() immediately
called find() to determine the new state. notify() is async so
activeNotifications didn't yet reflect the post, find() returned null,
and the tile was set to STATE_INACTIVE despite the timer having
started. The Boolean hint was only consulted to force INACTIVE on
cancel; nothing forced ACTIVE on start, so the start path raced.

show() and update() now return the notification they post, toggle()
returns it (or null on cancel), and refreshTile() takes a Notification?
that callers respond to directly. The find() fallback is preserved as
the default for callers that aren't reacting to a state change
(onStartListening on QS open).
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.

Quick tile intermittently greyed out

1 participant