Both home-screen widgets kept highlighting yesterday as "today" after the date changed — and the agenda kept dimming events against yesterday — until the user paged the month arrows or removed and re-added the widget. The data layer was never the problem: the month cache guard already drops its window as soon as the anchor date differs, which is exactly why an arrow tap fixed it instantly. What was missing was anything to *trigger* a redraw at the day boundary. The manifest asked for DATE_CHANGED and the receiver's KDoc 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 — this has never worked on any device the app supports. That left only updatePeriodMillis, which the system defers in doze and OxygenOS-style skins throttle harder still; the reporter's is a 30-minute backstop that in practice never ran. So the app now holds its own wake-up. WidgetRolloverScheduler arms a single alarm for just after the next local midnight and every firing re-arms the next, the same shape as ReminderAlarmScheduler. It is deliberately inexact: setAndAllowWhileIdle needs no permission and survives doze, and a rollover a few minutes late is invisible on a sleeping screen — exact alarms stay reserved for reminder snooze. The target is the *actual* start of the day rather than a literal 00:00, so it stays right in Havana, where DST means midnight does not happen, and it walks on past a whole date skipped by a date-line move. The alarm is only armed while a widget is actually placed: onEnabled/onDisabled on both Glance receivers re-sync it, and sync() cancels only when neither kind is left, so removing the month widget never stops the agenda one rolling over. WidgetUpdateReceiver re-arms on everything that can invalidate the alarm — boot and package-replace wipe it, a clock or timezone change moves the boundary it was aimed at — and CalendulaApp does the same on start, which is what arms existing installs that upgrade into this without re-adding their widget. It also gained an action allowlist, matching ReminderScheduleReceiver: it is exported and the broadcasts it takes are protected. DATE_CHANGED stays in the filter as a free extra for any OEM that does deliver it, but the docs no longer claim anything depends on it. Adding the two overrides makes the receivers as structurally alike as the widgets they wrap, which is how #89 collapsed Glance's provider map, so the keep rule now covers GlanceAppWidgetReceiver as well. Verified in the releaseTest mapping: all four classes keep their real names. Tests cover the arithmetic that decides when to wake — ordinary days, the re-arming instant itself, both DST transitions, a zone whose midnight does not exist, a half-hour offset, and the same instant seen from two zones. Closes #228.
60 lines
3.7 KiB
Prolog
60 lines
3.7 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
|
|
|
|
# Same hazard one level up: MonthWidgetReceiver and AgendaWidgetReceiver are
|
|
# also structurally identical (same supertype, same overrides, only a differing
|
|
# property initializer), and Glance's provider map is keyed off the receiver
|
|
# component too. Manifest-declared components are normally kept anyway, but this
|
|
# is cheap and makes the invariant explicit rather than incidental.
|
|
-keep class * extends androidx.glance.appwidget.GlanceAppWidgetReceiver
|