Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 20 additions & 26 deletions .cursor/rules/main-rules.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,17 @@ alwaysApply: true

# Code Changes

Non-test code changes are now permitted for:
This is a legacy codebase (2016) with a robust test suite added in 2024-25.

**Permitted without confirmation:**
- Bug fixes with corresponding test coverage
- Refactoring with existing test coverage
- Removal of deprecated features (QuietHours, CalendarEditor)

For new features or significant changes, add tests first.

Previously the name of this rule was

dont change any non test code without getting confirmation first

and its body:

dont change any non test code without getting confirmation first. this is a very old application I'm working on making a thorough test suite for before making any changes.

now the test suite is a lot more robust we've relaxed the rule a bit but still be cognizant of it
**Requires tests first:**
- New features or significant changes

When in doubt, add tests first.

# Work to make tests as faithful to the real code as possible

Expand All @@ -32,34 +26,34 @@ its ok to mock out core android apis that the instrumentation testsuite doesn't

# Don't try to boil the ocean. Dont try to make big sweeping changes when more focused ones will do

Always think of the minimum viable solution to a problem or change to make. make sure that works and then build on top of it. break things down into small testable pieces first. That said NO CHEATING! I.e. don't comment out or skip a test to solve a problem unless its just a temporary bandaid while working on something more important.
Always think of the minimum viable solution to a problem or change to make. make sure that works and then build on top of it. break things down into small testable pieces first. That said

**NO CHEATING!** I.e. don't comment out or skip a test to solve a problem unless its just a temporary bandaid while working on something more important.

# keep all code implementations as consise as possible.

Everything it needs nothing it doesn't. Every new line of code is one that potentially doesn't work 😄


# Please check the documentation if you are implementing something potentially complex or nonstandard.
# Check documentation if implementing something potentially complex or nonstandard.

often there are things we've learned already i.e.
See `docs/README.md` for the full documentation index. often there are things we've learned already i.e.

docs/dev_completed/constructor-mocking-android.md
**Key references:**

documents how through a lot work an reserch we learned
- `docs/dev_completed/constructor-mocking-android.md` - **MockK limitations**: `mockkStatic`, `mockkConstructor`, and `anyConstructed` almost always fail in Android instrumentation tests. Use dependency injection patterns instead.

mockKStatic, mockConstructor and anyConstructed ALMOST ALWAYS FAIL. And aren't worth trying anymore where there are better alternatives.
- `docs/architecture/calendar_monitoring.md` - Deep detail on how calendar monitoring works (EVENT_REMINDER broadcasts, manual rescans, etc.)

also
- `docs/architecture/clock_implementation.md` - How `CNPlusClockInterface` enables testable time-dependent code

docs/calendar_monitoring.md

goes into DEEP detail about how calendar monitoring works in the app
# Copyright Headers

For new or updated copyright headers, use:

# any new or updated copyright headers please attribute to

```
Copyright (C) 2025 William Harris ([email protected])
```

William inherited the application from Sergey Parshin

so any time you see the need to update those or create new ones please attribute to William
(William inherited the application from Sergey Parshin in 2020)
92 changes: 58 additions & 34 deletions docs/architecture/calendar_monitoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

Follows `android.intent.action.EVENT_REMINDER` through the code to registerNewEvent

```
Calendar Provider EVENT_REMINDER Broadcast
Expand Down Expand Up @@ -45,13 +46,20 @@ CalendarMonitor.onProviderReminderBroadcast
├──► ApplicationController.postEventNotifications: Post notifications for eventsToPost
├──► CalendarMonitor.setAlertWasHandled: Mark all events as handled
├──► CalendarMonitor.setAlertWasHandled: Mark eventsToSilentlyDrop as handled
├──► CalendarProvider.dismissNativeEventAlert: Dismiss native alerts for eventsToSilentlyDrop
├──► CalendarMonitor.setAlertWasHandled: Mark eventsToPost as handled
├──► CalendarProvider.dismissNativeEventAlert: Dismiss native alerts for eventsToPost
├──► CalendarProvider.dismissNativeEventAlert: Dismiss native calendar alerts
├──► ApplicationController.afterCalendarEventFired: Reschedule alarms and notify UI
└──► ApplicationController.afterCalendarEventFired: Reschedule alarms and notify UI
└──► CalendarMonitor.launchRescanService: Trigger rescan service
```

``` mermaid
```mermaid
flowchart TD
A[Calendar Provider EVENT_REMINDER Broadcast] --> B[EventReminderBroadcastReceiver.onReceive]
B --> C[CalendarMonitor.onProviderReminderBroadcast]
Expand Down Expand Up @@ -81,10 +89,16 @@ flowchart TD
T --> U[Verify storage]
U --> V[Add to eventsToPost]

V --> W[Post notifications]
W --> X[Mark as handled]
X --> Y[Dismiss native alerts]
Y --> Z[Reschedule alarms and notify UI]
V --> W[Post notifications for eventsToPost]
W --> X[Mark eventsToPost as handled]
X --> Y[Dismiss native alerts for eventsToPost]

L --> X2[Mark eventsToSilentlyDrop as handled]
X2 --> Y2[Dismiss native alerts for eventsToSilentlyDrop]

Y --> Z[afterCalendarEventFired]
Y2 --> Z
Z --> AA[launchRescanService]
```

Note: The broadcast receiver for EVENT_REMINDER is registered with the highest possible priority (2147483647) to ensure reliable event handling.
Expand All @@ -93,6 +107,7 @@ Note: The broadcast receiver for EVENT_REMINDER is registered with the highest p

Follows `android.intent.action.PROVIDER_CHANGED` through the code to registerNewEvent

```
onCalendarChanged
Expand All @@ -112,7 +127,7 @@ CalendarMonitorService.onHandleIntent
│ │
│ ├──► CalendarMonitorManual.scanNextEvent
│ │ │
│ │ ├──► CalendarProvider.getEventAlertsForInstancesRange
│ │ ├──► calendarProvider.getEventAlertsForInstancesInRange
│ │ │
│ │ ├──► filterAndMergeAlerts
│ │ │ │
Expand All @@ -135,36 +150,36 @@ ManualEventAlarmBroadcastReceiver receives alarm
│ ├──► Check timing condition
│ │ (nextEventFireFromScan < currentTime + ALARM_THRESHOLD)
│ │
──► CalendarMonitorManual.manualFireEventsAt_NoHousekeeping
├──► MonitorStorage.getAlertsAt/getAlertsForAlertRange
├──► registerFiredEventsInDB
│ │
│ └──► ApplicationController.registerNewEvents
└──► markAlertsAsHandledInDB
CalendarMonitorService processes final intent
Parameters:
- alert_time=reminderTime
- rescan_monitor=true
- reload_calendar=false
- start_delay=0
──► CalendarMonitorManual.manualFireEventsAt_NoHousekeeping
├──► MonitorStorage.getAlertsAt/getAlertsForAlertRange
├──► registerFiredEventsInDB
│ │
│ └──► ApplicationController.registerNewEvents
└──► markAlertsAsHandledInDB
│ ├──► ApplicationController.afterCalendarEventFired (if events fired)
│ │
│ └──► launchRescanService
│ Parameters:
- rescan_monitor=true
- reload_calendar=true
```

Note: The CalendarMonitorService uses a wake lock during the rescan process to ensure reliable operation, especially when processing calendar changes and firing events.

``` mermaid
```mermaid
flowchart TD
A[onCalendarChanged] --> B[CalendarMonitor.launchRescanService]
B --> C[Intent: CalendarMonitorService]
C -->|Parameters:<br/>start_delay=2000<br/>rescan_monitor=true<br/>reload_calendar=true<br/>user_action_until=0| D[CalendarMonitorService.onHandleIntent]
B --> C["Intent: CalendarMonitorService<br/>(start_delay=2000, rescan_monitor=true,<br/>reload_calendar=true, user_action_until=0)"]
C --> D[CalendarMonitorService.onHandleIntent]

D --> E[CalendarMonitor.onRescanFromService]
E --> F[CalendarMonitorManual.scanNextEvent]

F --> G[CalendarProvider.getEventAlertsForInstancesRange]
F --> G[calendarProvider.getEventAlertsForInstancesInRange]
F --> H[filterAndMergeAlerts]
H --> I[MonitorStorage.getAlertsForInstanceStartRange]
H --> J[Update MonitorStorage]
Expand All @@ -175,15 +190,16 @@ flowchart TD
M[Time advances past reminder time] --> N[ManualEventAlarmBroadcastReceiver]
N --> O[CalendarMonitor.onAlarmBroadcast]

O --> P{Check timing condition<br/>nextEventFireFromScan <br/> currentTime + ALARM_THRESHOLD}
O --> P{nextEventFireFromScan < currentTime + ALARM_THRESHOLD?}
P -->|true| Q[manualFireEventsAt_NoHousekeeping]

Q --> R[MonitorStorage.getAlertsAt]
Q --> S[registerFiredEventsInDB]
S --> T[ApplicationController.registerNewEvents]
Q --> U[markAlertsAsHandledInDB]

V[CalendarMonitorService final intent] -->|Parameters:<br/>alert_time=reminderTime<br/>rescan_monitor=true<br/>reload_calendar=false<br/>start_delay=0| D
U --> V[afterCalendarEventFired]
V --> W["launchRescanService<br/>(rescan_monitor=true, reload_calendar=true)"]
```

## Additional Calendar Monitoring Triggers
Expand All @@ -192,6 +208,7 @@ Besides the two main flows above, the Calendar Monitor can be triggered through

### System Boot

```
BOOT_COMPLETED Broadcast
Expand All @@ -207,9 +224,11 @@ ApplicationController.onBootComplete
CalendarMonitor.launchRescanService
(Same flow as PROVIDER_CHANGED)
```

### Application Update

```
MY_PACKAGE_REPLACED Broadcast
Expand All @@ -225,9 +244,11 @@ ApplicationController.onAppUpdated
CalendarMonitor.launchRescanService
(Same flow as PROVIDER_CHANGED)
```

### Time or Timezone Changes

```
TIME_SET or TIMEZONE_CHANGED Broadcast
Expand All @@ -236,17 +257,19 @@ TimeSetBroadcastReceiver.onReceive
ApplicationController.onTimeChanged
├──► Reschedule alarms
├──► alarmScheduler.rescheduleAlarms
CalendarMonitor.onSystemTimeChange
CalendarMonitor.launchRescanService
(Same flow as PROVIDER_CHANGED)
```

### Periodic Rescan

```
System-scheduled Alarm
Expand All @@ -257,4 +280,5 @@ CalendarMonitor.onPeriodicRescanBroadcast
CalendarMonitor.launchRescanService
(Same flow as PROVIDER_CHANGED)
(Same flow as PROVIDER_CHANGED)
```
9 changes: 3 additions & 6 deletions docs/architecture/clock_implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,7 @@ The clock refactoring has been successfully completed across the entire applicat
7. **Notification Components**: All notification-related components use the clock interface
8. **Test Infrastructure**: Comprehensive test support with advanced time manipulation capabilities

## Future Enhancements
## Potential Future Enhancements

1. Implement a dependency injection mechanism for providing the clock to components
2. Create a ClockProvider singleton to simplify access in components where direct injection is difficult
3. Add more comprehensive time-based tests leveraging the new interface
4. Consider expanding the test clock implementation with additional time manipulation features
5. Add detailed logging for time-related operations to aid in debugging
1. Add more detailed logging for time-related operations to aid in debugging (some exists, could be expanded)
2. Create a ClockProvider singleton for components where lambda injection is awkward (currently using `clockProvider` lambdas which work well)
Loading
Loading