feat(widgets): next alarm, timer and clock home-screen widgets

Three Glance widgets, mirroring state the app already computes rather
than owning any of their own: the next alarm (AlarmEngine.upcoming()),
the live pill's subject timer (same selection logic, no ticker of its
own — a running countdown is the platform chronometer, exactly like
the ring notification), and the local time growing to world clocks at
larger sizes. WidgetSync collects the same read-only flows the shell
already reads and redraws on change, event-driven (updatePeriodMillis
= 0) — it touches no engine, only re-renders, and covers every write
path including SystemEventReceiver's since the process is already
alive whenever those run.

Tapping a widget reuses the same internal navigation actions the
AlarmClock contract's door already produces; the timer buttons send
the same unexported broadcasts its notification does. A widget is
just another caller of machinery that already exists.

ManifestRulesTest is updated deliberately: three GlanceAppWidgetReceivers
join the exported set, each pinned to exactly the platform-required
APPWIDGET_UPDATE filter and no permission — no new permission is
requested anywhere in this change.
This commit is contained in:
2026-10-01 22:32:07 +02:00
parent a11396b058
commit fb81bfbf2e
25 changed files with 1039 additions and 4 deletions
+42
View File
@@ -232,6 +232,48 @@
android:name="autoStoreLocales"
android:value="true" />
</service>
<!-- The three M10 home-screen widgets (docs/PLAN.md §9). Each is a
GlanceAppWidgetReceiver, and each must be exported with only the
APPWIDGET_UPDATE filter — that is how the launcher delivers a
widget update, and the platform requires exported="true" for a
widget provider to be pickable at all. No permission guards it,
same as any other app's widget: APPWIDGET_UPDATE is a
platform-delivered broadcast, never a door another app can open by
naming the action itself. updatePeriodMillis is 0 in every
provider-info XML — WidgetSync is the one refresh trigger. -->
<receiver
android:name=".widget.NextAlarmWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_next_alarm_info" />
</receiver>
<receiver
android:name=".widget.TimerWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_timer_info" />
</receiver>
<receiver
android:name=".widget.ClockWidgetReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_clock_info" />
</receiver>
</application>
</manifest>