The countdown used the stock Material snackbar, while Agendula and
Calendula confirm with floret-kit's SnackChip. It now opens as that chip:
bottom-start in the FAB's band on the list, ellipsising short of the FAB,
and in the editor's snackbar slot so it follows the predictive-back
preview. It fills the band's height, as an Undo chip does in the other
apps, and sits on the secondary container to stand apart from the cards.
The chip has no timer, so AlarmSetChipBand holds it for a short
snackbar's four seconds, stretched as the accessibility settings ask.
The hand-over is unchanged: a chip cut short stays pending for the next
resumed screen.
Beside the FAB there is room for about 308 dp, so the message is now
"Alarm in 7 hours and 12 minutes" rather than "Alarm set for … from now",
which would have lost its number to the ellipsis.
Refs #19
A refused state change is now remembered as owed and forced by every pass
until it lands, instead of recording the opposite state, which could hide
the next real boundary. Any refusal, in the rule rewrite or the state
change, pulls the slot in to a retry, and the slot is armed in a finally
so a throwing step can't leave it unarmed. The wake-up that ends the mode
is armed allow-while-idle (one such fire a night), so Do Not Disturb no
longer lingers into the morning behind a doze deferral.
The manifest filter for the access-changed broadcast is gone: the
platform only delivers it to runtime-registered receivers, and the
screen's resume check covers it. A backup written before bedtime existed
now keeps the current bedtime settings, and a restore goes through the
engine's own field comparison, so a reminder-only difference leaves a
hand-switched mode alone.
Refs #1
A removal the system refuses while turning bedtime off now keeps the slot
armed fifteen minutes out as its retry, instead of cancelling it and
leaving Do Not Disturb on until some unrelated launch. A refused state
change records the opposite state so the next pass retries it, and a
rule that was rewritten (which clears its condition on Android 10-14) is
switched regardless. A refused rewrite is remembered and retried by every
pass until it lands, rather than resting on the one edit that asked.
Refs #1
Without exact-alarm access the slot falls back to a ten-minute window
instead of plain set(), whose slack could end an eight-hour night hours
late. A freshly opened bedtime screen reads whether bedtime is on from the
store, not from its still-loading state, so access granted while the
screen was closed creates the rule. A restore that rolled back no longer
forces the mode.
Refs #1
Android rations allow-while-idle wake-ups per app in doze, and the timer's
expiry slot and the alarm's fallback share that ration, so a 07:00
wake-up fire could hold back a timer due at 07:04. The bedtime slot is
now a plain exact alarm that doze may defer, which can move a mode
boundary but never a ring. A restore of unchanged bedtime settings no
longer forces the mode, and the screen skips its rule check while bedtime
is off.
Refs #1
Only a bedtime before 06:00 now counts as the small hours after the
chosen evening, so a day sleeper's 08:00 bedtime runs on the days picked.
A rule whose removal was refused while turning bedtime off in the daytime
is removed by the next pass, and a restored reminder length the picker
doesn't offer stays choosable.
Refs #1
A bedtime before noon now belongs to the evening before, so Sunday to
Thursday nights at 00:30 run early Monday to early Friday, not Saturday
night through Wednesday night. Turning bedtime off clears the "last
applied" state only once the rule is really gone, so a refused removal
is retried instead of leaving Do Not Disturb on. Reopening the screen
after granting access elsewhere creates the rule even if the system's
broadcast never arrived. The empty-mask rule lives in one place.
Refs #1
Whether the wake time is the same night or the next morning is now
decided on the local times, so a bedtime in a spring-forward gap no
longer turns a 45-minute window into a day-long one. Screen edits start
undispatched so they reach the engine's lock in the order they were made,
and an edit that rewrites the rule also re-asserts the mode, in case the
platform reset its state.
The launch-time bedtime pass runs even if an earlier engine pass failed,
a restore that never applied settings no longer forces the mode, and a
user who never turned bedtime on pays nothing for it at launch or boot.
The screen says when equal bedtime and wake-up times mean it never starts.
Refs #1
A rule recreated by a pass (deleted in Settings, access re-granted) now
gets its state set even when no boundary was crossed, and the applied
state is recorded only once the system accepted it. Edits that can't move
a boundary (reminder, effects) no longer re-assert the mode over a manual
override, and the screen's writes run on the application scope so leaving
right after a change can't cut it short. A late reminder counts the
minutes actually left, and one slot fire reads the zone once.
An equal bedtime and wake time is now no window instead of a day-long one,
which on every night meant Do Not Disturb never ended, and an empty days
mask in prefs reads as every night, as a backup's does. The rule-id extras
are translated to the bedtime action in MainActivity, keeping the routing
table keyed on the action alone.
Refs #1
Settings opens a rule's configuration activity with no action, only the
component and the rule id, so MainActivity now routes on the rule-id
extras. Each engine pass reads the clock once, so the mode's state and the
next slot can't disagree around a boundary, and non-edit passes switch the
mode only when the schedule crossed a boundary since Clockula last set it
(persisted), which also catches up a wake-up missed during a force stop
while leaving a mode switched on by hand alone. A reminder fire that also
ends a window now turns the mode off, and a reminder the schedule moved
away from is withdrawn.
The rule is only rewritten when a field it carries changes, and a rewrite
keeps the existing policy if it still allows alarms, so Android 10-14 no
longer loses the user's Do Not Disturb edits. The bedtime receiver, screen
and fallback alarm call are guarded, the boot-path resync runs even if an
earlier engine failed, and a backup with no nights restores every night.
Refs #1
With seconds on every row recomposed each second and rebuilt its custom
actions from new lambdas, so TalkBack saw changed semantics every
second. The callbacks are remembered per id and the action list per
label and callback. The prefs isolation test now uses a barrier on the
same flow, a weak settings assertion is gone, and a clock set is tested
to show at once and restart the minute tick.
Picking "Timer expired" as the default alarm or timer sound showed "Custom" in Settings, because the summary knew only the device default and silence. It now names the bundled sound, as the editors already do.
ARCHITECTURE.md now names the exact bundled sentinel instead of a prefix the code does not recognise, lists clockula://media values in the alarm column, gives the timer column the same values, and says RingtoneCatalog lists the bundled sounds too.
Refs #14
Clockula can now turn on a Do Not Disturb mode of its own at bedtime and
off at the wake-up time, on the nights the user picks, with an optional
reminder before it starts. Digital Wellbeing's Bedtime mode can't be
driven by a third-party app, so this is a separate rule: a typed mode
with grayscale, dimmed wallpaper, dark theme and hidden always-on display
on Android 15+, and a plain DND schedule rule on Android 10-14.
The rule's policy always allows alarms (and media). BedtimeEngine owns one
AlarmManager slot of its own, on its own receiver and request code, and
every bedtime call on the boot, time-change and launch paths runs after
the alarm and timer passes, guarded. The rule id is never stored: the
platform lists an app's own rules, so restores and reinstalls find or
recreate it. Turning bedtime off removes the rule. The screen walks the
user through granting DND access and explains the Wellbeing overlap.
Settings are part of the JSON backup; older files restore bedtime as off.
Refs #1
ARCHITECTURE.md still said every non-silent ringtone_uri is a content URI and listed the picker's rows without the bundled "Timer expired" sound, so a reader could Uri.parse the clockula://tone/ sentinel directly. It now names the sentinel, BundledTones as its only resolver, and where the tone sits in the picker.
A view-model test now covers a device whose only listed tone is the bundled one, which must still show the "no alarm sounds" banner. The existing test only checked an empty list, so it passed even if the bundled tone were counted as a device sound.
Refs #14
Keep §5's own claim that only crash reports leave the phone, say the
dependency would ship in every channel, and keep the standalone app's
distribution caveat.
Refs #27
The row's seconds time was capped at 168dp everywhere, so on a tablet a
larger font scale was shrunk back for no reason. The cap now grows to
45% of the window, with 168dp as the floor, and the fitted size is
recomputed when the text measurer changes. A row the column has not
re-seeded yet is skipped rather than crashing, and the source's KDoc
is re-wrapped.
AlarmClock intents only reach apps on the same device and can't read
state, so they are no route for a watch. Cite PRIVACY.md §4 and §5
together and say "no product flavours" precisely.
Refs #27
Cite PRIVACY.md's own wording, name F-Droid's non-free-dependency rule,
and note that third-party watch apps can already drive Clockula through
the AlarmClock intents.
Refs #27
PRIVACY.md already documents Auto Backup and crash reports as ways data
can leave the phone, and there is a single build rather than an F-Droid
one. Say that, and call the entry blocked rather than unscoped.
Refs #27
Any phone-to-watch channel sends data off the phone, which the privacy
policy rules out today, so the entry now states that decision and the
standalone alternative without speculating about notification bridging.
Refs #27
A new setting in Settings > Alarms: while the ring screen shows, the
volume buttons change the volume (the default, and what they did
before), snooze, dismiss, or do nothing. It is app-wide, stored next to
the clock defaults, and included in the backup; a backup without it
restores "change the volume".
A press is made without looking, so VolumeButtonPolicy keeps it from
ending an alarm more finally than the ring screen would. Dismiss with a
dismiss challenge set snoozes instead, so the challenge still stands.
With no snoozes left, Snooze (and Dismiss behind a challenge) falls
back to changing the volume, never to the engine's refused-snooze path,
which dismisses. The key's snooze also asks the engine to keep ringing
if the limit turns out to be reached after all (AlarmEngine.snooze's new
dismissIfRefused). The ring screen decides whether a press is its own
on key down and keeps that for the release, leaves the keys alone once
another alarm has taken over the ring, and ignores a second snooze or
dismiss while the first is still running.
Only the ring screen handles the keys. While the ring is a heads-up over
another app, a background app gets no key events, and the one way
around that does not hold up: a remote-playback MediaSession whose
VolumeProvider takes the keys. On Android 13 and later
MediaSessionRecord.canHandleVolumeKey refuses such a session from an app
without a MediaRouter2 routing session, which Clockula's would be, so on
current Pixels the keys never reach it. Where it does work, it loses to
whatever app is in front or played last, shows a cast-volume slider and
a phantom media player, and takes media buttons while the alarm rings.
AOSP DeskClock and BlackyHawky's Clock also handle the keys in their
alarm activity only. ARCHITECTURE.md records the limitation.
Fixes#18
Ringing notifications are foreground-service notifications, so making
them non-ongoing wouldn't bridge them; CompanionDeviceManager carries no
data; and watches pair through their makers' apps. Say only what holds
and note that a watch build has no F-Droid channel.
Refs #27
Ring and timer notifications are ongoing, which Wear OS doesn't bridge,
and bridging needs Google's Wear OS app anyway. Quote the privacy policy
exactly, drop the unchecked "only channel" claim, and list bridging the
ring as an option of its own.
Refs #27
A companion needs Google's Wearable Data Layer, which is a proprietary
service the privacy policy and the F-Droid build rule out. The roadmap
entry now records that constraint, what the bridged notifications
already give a watch, and the options if it is picked up.
Refs #27
A spot the clock comes back to, after the time is set back, is now the
newest frame, so the copy fading out is never the one dropped. A
keyboard dock or a connected keyboard no longer recreates the in-app
screen saver either.
The in-app screen saver also handles uiMode, density, font scale and
locale changes in place, so docking it or the dark-theme schedule no
longer recreates it at night brightness. Only a move crossfades; a new
next alarm or a settings change redraws in place, and the outgoing copy
keeps its own minute. The AM/PM split skips quoted pattern text, and
rememberLocalTimeFormatter reads the same currentLocale() as the screen
saver.
The dream now turns its window down to the same near-zero brightness as
the in-app screen saver, not just to the system's dim level, and builds
its ComposeView from the window's context rather than the service's.
The in-app screen saver finishes once it is no longer shown, so waking
the phone shows the lock screen, and rotating it into a stand no longer
recreates it. The outgoing minute of the crossfade is hidden from screen
readers.
The settings screen Android's screen saver list opens gets a task of its
own and stays out of recents, so it never lands in or clears the app's
task, and its bars follow the app's theme. The on-demand screen saver is
noHistory, so leaving it by Home does not bring the app back to it later,
and it only touches the keep-screen-on flag when charging starts or stops.
The next-alarm line uses the same 12/24-hour rule as the big readout,
joined to the day by a translatable string; the minute's crossfade shows
the old minute fading out rather than two copies of the new one.