The month and agenda widgets did not roll over at midnight. They kept highlighting yesterday as "today", and the agenda kept dimming events against yesterday, until the user paged the month arrows or removed and re-added the widget. **What was wrong** The manifest asked for `DATE_CHANGED` and `WidgetUpdateReceiver`'s docs presented it as the rollover mechanism, but `DATE_CHANGED` is not on the implicit-broadcast exemption list, so a manifest-declared receiver has not been given it since Android 8. That left only `updatePeriodMillis`, which the system defers in doze and OEM skins throttle harder still. The data layer was fine all along - the month cache guard already drops its window when the anchor date changes, which is why an arrow tap fixed it instantly. **What changed** - `WidgetRolloverScheduler` arms a single alarm for just after the next local midnight and re-arms on every firing, the same shape as `ReminderAlarmScheduler`. Inexact (`setAndAllowWhileIdle`): no permission, survives doze, and exact alarms stay reserved for reminder snooze. It targets the actual start of day, not a literal 00:00, so it holds where DST means midnight never happens. - Armed only while a widget is placed, via `onEnabled`/`onDisabled` on both Glance receivers; `sync()` cancels only when neither kind is left. Re-armed from boot, package-replace, time and timezone changes, app start (which is what arms existing installs upgrading into this), and from `onUpdate`, so an alarm dropped by a force-stop or an OEM freeze heals itself. - Paging the month widget forward and back no longer pins it to that month. `ShiftMonthAction` stored an absolute index on every tap, so the workaround people used to force a redraw quietly stranded the widget on whatever month was current at the time. - `DATE_CHANGED` stays in the filter as a free extra, but nothing depends on it and the docs no longer claim otherwise. - Keep rule extended to `GlanceAppWidgetReceiver`, since the two receivers are now as structurally alike as the widgets that #89 collapsed. **Verification** `testDebugUnitTest` (775 tests, 0 failures), `lintDebug` and `assembleDebug` all pass. `assembleReleaseTest` builds and the R8 mapping confirms `MonthWidget`, `AgendaWidget` and both receivers keep their real names. 12 new unit tests cover the next-midnight arithmetic: ordinary days, the re-arming instant itself, both DST directions on frozen historical transitions, a zone whose midnight does not exist, a half-hour offset, two zones seeing the same instant, and that the receiver's action guard admits the alarm's action. Not verified on a device - the overnight rollover on an OxygenOS-class device is the one thing that needs a real test before release. Closes #228. Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de> Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/246
59 lines
3.6 KiB
Prolog
59 lines
3.6 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: the two receivers are nearly as alike, and the
|
|
# provider map is keyed off the receiver component too. Redundant today (AGP's
|
|
# manifest-derived rules cover them), but #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
|