Files
calendula/app/proguard-rules.pro
Jean-Luc Makiola 7e7079df00 Widgets: close the rest of the rollover gaps (#228)
Review follow-up on the midnight alarm.

The month widget could still show the wrong month after the rollover, and by way
of the exact workaround #228's reporter described. ShiftMonthAction stored an
absolute index on every tap, so paging forward and back — which looks like a
no-op and was how people forced a redraw — silently pinned the widget to the
month that was current at the time. It kept redrawing correctly and kept drawing
August into September, today's circle nowhere on the grid. Landing back on the
current month now clears the key instead of writing it, so the widget goes back
to following the date. Paging somewhere else and staying there is left alone;
that one is a choice, and the today button undoes it.

The alarm also had no way back once something dropped it without telling us — a
force-stop, a battery-restricted transition, an OEM freeze — short of the user
opening the app. onUpdate is the one wake-up the system still owns through
updatePeriodMillis, so both receivers re-arm from it. That doubles as a window
narrower: setAndAllowWhileIdle's delivery slack scales with how far out the alarm
was set, so re-arming every half hour keeps midnight's window at minutes rather
than hours.

Boot and package-replace now re-arm and stop there. The host sends
APPWIDGET_UPDATE after both, so the redraw was a second pair of wide provider
reads and RemoteViews serialisations in a cold process, exactly when the device
is busiest.

Smaller things: the unreachable lookahead fallback armed 5 seconds out, which
would have been a wake-loop rather than the late redraw the comment claimed —
an hour now. The action guard's comment was copied from ReminderScheduleReceiver
along with its claim that the broadcasts are protected; PROVIDER_CHANGED is not,
and the real reason the guard is worth having is that the receiver must stay
exported. Application start does its sync off the main thread now.

Tests moved onto frozen historical transitions — Berlin 2024 both ways, Havana
2018 — since the 2026 dates they used depend on DST rules that can still change
under a tzdata bump. The Berlin "fall back" case never repeated midnight, so it
now says what it actually checks (a 25-hour day must not overshoot) and a real
repeated-midnight zone, pre-2019 Sao Paulo, pins the known limitation instead of
implying it is handled. Dropped the two assertions that restated the code, and
added the one that was missing: that the receiver's action guard admits the
action the alarm is sent with, which is the single point where the whole thing
would fail silently.
2026-08-27 20:17:22 +02:00

62 lines
3.9 KiB
Prolog

# Keep Hilt-generated classes
-keep class dagger.hilt.** { *; }
-keep @dagger.hilt.android.HiltAndroidApp class *
# Compose Compiler may keep its own; defaults are fine
-dontwarn org.jetbrains.annotations.**
# Room database implementations (pulled in transitively via
# androidx.glance:glance-appwidget androidx.work androidx.room).
# The widgets rely on Glance, whose WorkManager backend stores state in a Room
# database. Under R8 full mode (AGP 9 default) the generated *_Impl subclasses
# of RoomDatabase lose their usable no-arg constructor / are marked abstract,
# so Room's reflective instantiation throws InstantiationException and the app
# crashes at startup with "Failed to create an instance of ...WorkDatabase".
# Keep the generated Room database implementations fully intact.
-keep class * extends androidx.room.RoomDatabase { *; }
-dontwarn androidx.room.paging.**
# Glance runs an @Composable's `actionRunCallback<T>()` by persisting the
# callback's fully-qualified class name into the click PendingIntent, then
# reflectively instantiating it (Class.forName(name).newInstance()) when the tap
# fires. Under R8 full mode (AGP 9 default) these ActionCallback classes — only
# ever referenced reflectively — get renamed or have their no-arg constructor
# stripped, so the lookup fails silently and the tap does nothing. In the month
# and agenda widgets that broke every run-callback control (the prev/next/today
# month arrows and the agenda refresh) in release builds while actionStartActivity
# taps, which ride a PendingIntent and need no reflection, kept working. Keep
# every ActionCallback's name and constructor intact.
-keep class * implements androidx.glance.appwidget.action.ActionCallback { <init>(...); }
# WorkManager instantiates an InputMerger reflectively (Class.newInstance) from
# the fully-qualified class name persisted in the WorkSpec, so the class must
# keep both its name and a no-arg constructor. Glance renders every widget
# through a WorkManager worker (androidx.glance.session.SessionWorker) whose
# default merger is androidx.work.OverwritingInputMerger. Under R8 full mode
# (AGP 9 default) that unused no-arg constructor was stripped, so WorkManager
# threw "OverwritingInputMerger has no zero argument constructor", the
# SessionWorker never ran, and widgets were stuck on their loading layout
# (a blank spinner) in release builds. Keep every InputMerger's name + ctor.
-keep class * extends androidx.work.InputMerger { <init>(...); }
# Glance identifies a widget by its GlanceAppWidget subclass's *canonical name*:
# GlanceAppWidgetManager persists a providerName -> receivers map under that
# string, and `updateAll` looks the widget's app-widget ids up through it. Under
# R8 full mode (AGP 9 default) MonthWidget and AgendaWidget — same supertype,
# same overrides, no distinguishing members — were horizontally merged into one
# class, so both receivers registered under the *same* provider name and
# `AgendaWidget().updateAll()` resolved the month widget's id too, redrawing a
# placed month widget as the agenda one on the next data change (#89). Keeping
# the real names also survives app updates, which would otherwise renumber the
# obfuscated name and orphan the stored mapping.
-keep class * extends androidx.glance.appwidget.GlanceAppWidget
# Belt and braces one level up: MonthWidgetReceiver and AgendaWidgetReceiver are
# nearly as alike (same supertype, same overrides, only a differing property
# initializer), and Glance's provider map is keyed off the receiver component
# too. AGP's manifest-derived keep rules already cover them, and the rule above
# keeps the two widgets distinct enough that the receivers' constructors differ
# so this is redundant today. It is here because #89 cost a release to diagnose
# and the guarantee should not rest on a component staying in the manifest.
-keep class * extends androidx.glance.appwidget.GlanceAppWidgetReceiver