diff --git a/docs/alarms/000-UNIFIED-ALARM-DIRECTIVE.md b/docs/alarms/000-UNIFIED-ALARM-DIRECTIVE.md index d2a5e12..1c364e9 100644 --- a/docs/alarms/000-UNIFIED-ALARM-DIRECTIVE.md +++ b/docs/alarms/000-UNIFIED-ALARM-DIRECTIVE.md @@ -3,8 +3,9 @@ **Author:** Matthew Raymer **Status:** Active – Master Coordination Directive **Scope:** Android & iOS, Capacitor plugin, alarms/schedules/notifications -**Version:** 1.0.0 -**Last Updated:** November 2025 +**Version:** 1.1.0 +**Last Updated:** November 2025 +**Last Synced With Plugin Version:** v1.1.0 --- @@ -25,6 +26,8 @@ Unify all existing alarm/notification documents into a **coherent, layered syste **This directive is the top of the stack.** If any lower-level document conflicts with this one, **this directive wins** unless explicitly noted. +**Mission Statement**: This directive ensures that every alarm outcome on every platform is predictable, testable, and recoverable, with no undocumented behavior. + **⚠️ ENFORCEMENT RULE**: No new alarm-related documentation may be created outside Documents A, B, C, or P1–P3. All future changes must modify these documents, not create additional standalone files. --- @@ -322,32 +325,28 @@ When referencing between documents, use this format: ## 7. Canonical Source of Truth Rules -### 7.1 Scenario Detection - -**Only ReactivationManager.kt** performs scenario detection. No other component (BootReceiver, DailyNotificationPlugin, etc.) implements detection logic. - -**Corrected Scenario Model** (from Phase 2): -```kotlin -enum class RecoveryScenario { - COLD_START, // Process killed, alarms may or may not exist - FORCE_STOP, // Alarms cleared, DB still populated - BOOT, // Device reboot - NONE // No recovery required (warm resume or first launch) -} -``` - -### 7.2 Recovery Logic - -**Only ReactivationManager.kt** implements recovery logic. BootReceiver only sets flags and queues ReactivationManager. - -### 7.3 Platform Facts +### 7.1 Platform Facts **Only Doc A** contains platform facts. All other docs reference Doc A, never duplicate platform behavior. -### 7.4 Requirements +**Reference Format**: `[Doc A §X.Y]` - See [Platform Capability Reference](./01-platform-capability-reference.md) + +### 7.2 Requirements **Only Doc C** defines plugin requirements. Phase docs implement Doc C requirements. +**Reference Format**: `[Doc C §X.Y]` - See [Plugin Requirements](./03-plugin-requirements.md) + +### 7.3 Implementation Details + +**Only Phase docs (P1-P3)** contain implementation details. Unified Directive does not specify code structure or algorithms. + +**Reference Format**: `[Phase N §X.Y]` - See Phase implementation directives + +### 7.4 Test Results + +**Only Doc B** contains actual test results and observed behavior. Doc B references Doc A for expected OS behavior and Doc C for expected plugin behavior. + --- ## 8. Conflict Resolution @@ -410,6 +409,18 @@ When a conflict is found: 2. Expand exploration scenarios 3. Create regression test suite based on Doc B +### ⚠️ iOS Parity Activation Milestone + +**iOS parity work begins ONLY after**: + +1. **Doc A contains iOS matrix** - All iOS platform facts documented with labels (see [Doc A](./01-platform-capability-reference.md#3-ios-notification-capability-matrix)) +2. **Doc C defines iOS limitations and guarantees** - All iOS-specific requirements documented (see [Doc C](./03-plugin-requirements.md#1-plugin-behavior-guarantees--limitations)) +3. **No references in Phase docs assume Android-only behavior** - All Phase docs are platform-agnostic or explicitly handle both platforms + +**Blocking Rule**: No iOS implementation work may proceed until these conditions are met. + +**Enforcement**: Phase docs MUST reference Doc A for platform facts and Doc C for requirements. No platform-specific assumptions allowed. + --- ## 10. Change-Control Rules @@ -438,12 +449,20 @@ Any change to Docs A–C requires: | Doc | Path | Role | Drafted? | Cleaned? | In Use? | Notes | | --- | ------------------------------------- | ----------------- | -------- | -------- | ------- | ---------------------------------------- | -| A | `01-platform-capability-reference.md` | Platform facts | ☐ | ☐ | ☐ | Merge from existing platform docs | -| B | `02-plugin-behavior-exploration.md` | Exploration | ☐ | ☐ | ☐ | Based on exploration template | -| C | `03-plugin-requirements.md` | Requirements | ☐ | ☐ | ☐ | Consolidate from requirements doc | -| P1 | `android-implementation-phase1.md` | Impl – Cold start | ✅ | ☐ | ☐ | Exists, needs alignment with Doc C | -| P2 | `android-implementation-phase2.md` | Impl – Force stop | ✅ | ☐ | ☐ | Exists, needs alignment with Doc C | -| P3 | `android-implementation-phase3.md` | Impl – Boot | ✅ | ☐ | ☐ | Exists, needs alignment with Doc C | +| A | `01-platform-capability-reference.md` | Platform facts | ✅ | ✅ | ✅ | Created, merged from platform docs, canonical rule added | +| B | `02-plugin-behavior-exploration.md` | Exploration | ✅ | ✅ | ☐ | Converted to executable test harness | +| C | `03-plugin-requirements.md` | Requirements | ✅ | ⚠️ | ⚠️ | Enhanced with guarantees matrix, JS/TS contract, traceability - **in compliance** | +| P1 | `../android-implementation-phase1.md` | Impl – Cold start | ✅ | ✅ | ☐ | Aligned with Doc C | +| P2 | `../android-implementation-phase2.md` | Impl – Force stop | ✅ | ✅ | ☐ | Aligned with Doc C | +| P3 | `../android-implementation-phase3.md` | Impl – Boot | ✅ | ✅ | ☐ | Aligned with Doc C | + +**Doc C Compliance Milestone**: Doc C is considered complete **ONLY** when: +- ✅ Cross-platform guarantees matrix present +- ✅ JS/TS API contract with returned fields and error states +- ✅ Explicit storage schema documented +- ✅ Recovery contract with all triggers and actions +- ✅ Unsupported behaviors explicitly listed +- ✅ Traceability matrix mapping A → B → C → Phase **Legend:** - ✅ = Complete @@ -464,7 +483,7 @@ Any change to Docs A–C requires: **Status**: Active master coordination directive **Last Updated**: November 2025 -**Next Review**: After Docs A, B, C are created +**Next Review**: After implementation phases are complete --- @@ -481,3 +500,88 @@ Any change to Docs A–C requires: **Exception**: Only emergency bug fixes may proceed, but must be documented retroactively in the appropriate Doc A/B/C structure. +--- + +## 13. Prohibited Content Rules + +**The following content may NOT appear in any document except the specified one**: + +| Content Type | Allowed Only In | Examples | +| ------------ | --------------- | -------- | +| **Platform rules** | Doc A only | "Android wipes alarms on reboot", "iOS persists notifications automatically" | +| **Guarantees or requirements** | Doc C only | "Plugin MUST detect missed alarms", "Plugin SHOULD reschedule on boot" | +| **Actual behavior findings** | Doc B only | "Test showed alarm fired 2 seconds late", "Missed alarm not detected in scenario X" | +| **Recovery logic** | Phase docs only | "ReactivationManager.performRecovery()", "BootReceiver sets flag" | +| **Implementation details** | Phase docs only | Code snippets, function signatures, database queries | + +**Violation Response**: If prohibited content is found, move it to the correct document and replace with a cross-reference. + +--- + +## 14. Glossary + +**Shared terminology across all documents** (must be identical): + +| Term | Definition | +| ---- | ---------- | +| **recovery** | Process of detecting and handling missed alarms, rescheduling future alarms, and restoring plugin state after app launch, boot, or force stop | +| **cold start** | App launched from terminated state (process killed, no memory state) | +| **warm start** | App returning from background (process may still exist, memory state may persist) | +| **missed alarm** | Alarm where `trigger_time < now`, alarm was not fired (or firing status unknown), alarm is still enabled, and alarm has not been manually cancelled | +| **delivered alarm** | Alarm that successfully fired and displayed notification to user | +| **persisted alarm** | Alarm definition stored in durable storage (database, files, etc.) | +| **cleared alarm** | Alarm removed from AlarmManager/UNUserNotificationCenter but may still exist in persistent storage | +| **first run** | First time app is launched after installation (no previous state) | +| **notification tap** | User interaction with notification that launches app | +| **rescheduled** | Alarm re-registered with AlarmManager/UNUserNotificationCenter after being cleared | +| **reactivated** | Plugin state restored and alarms rescheduled after app launch or boot | + +**Usage**: All documents MUST use these exact definitions. No synonyms or variations allowed. + +--- + +## 15. Lifecycle Flow Diagram + +**Document relationship and information flow**: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Unified Directive (000) - Master Coordination │ +│ Defines structure, ownership, change control │ +└─────────────────────────────────────────────────────────────┘ + │ + │ References & Coordinates + │ + ┌───────────────────┼───────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ Doc A │ │ Doc B │ │ Doc C │ +│ Platform │ │ Exploration │ │ Requirements│ +│ Facts │ │ & Testing │ │ & Guarantees│ +└───────────────┘ └───────────────┘ └───────────────┘ + │ │ │ + │ │ │ + │ │ │ + │ │ │ + └───────────────────┼───────────────────┘ + │ + │ Implements + │ + ┌───────────────────┼───────────────────┐ + │ │ │ + ▼ ▼ ▼ +┌───────────────┐ ┌───────────────┐ ┌───────────────┐ +│ Phase 1 │ │ Phase 2 │ │ Phase 3 │ +│ Cold Start │ │ Force Stop │ │ Boot │ +│ Recovery │ │ Recovery │ │ Recovery │ +└───────────────┘ └───────────────┘ └───────────────┘ +``` + +**Information Flow**: +1. **Doc A** (Platform Facts) → Informs **Doc C** (Requirements) → Drives **Phase Docs** (Implementation) +2. **Doc B** (Exploration) → Validates **Phase Docs** → Updates **Doc C** (Requirements) +3. **Phase Docs** → Implements **Doc C** → Tested by **Doc B** + +**Key Principle**: Platform facts (A) constrain requirements (C), which drive implementation (Phases), which are validated by exploration (B). + diff --git a/docs/alarms/01-platform-capability-reference.md b/docs/alarms/01-platform-capability-reference.md new file mode 100644 index 0000000..0f91b37 --- /dev/null +++ b/docs/alarms/01-platform-capability-reference.md @@ -0,0 +1,468 @@ +# Platform Capability Reference: Android & iOS Alarm/Notification Behavior + +**Author**: Matthew Raymer +**Date**: November 2025 +**Status**: Platform Reference - Stable +**Version**: 1.1.0 +**Last Synced With Plugin Version**: v1.1.0 + +## Purpose + +This document provides **pure OS-level facts** about alarm and notification capabilities on Android and iOS. It contains **no plugin-specific logic**—only platform mechanics that affect plugin design. + +**This is a reference document** to be consulted when designing plugin behavior, not an implementation guide. + +**⚠️ CANONICAL RULE**: No other document may contain OS-level behavior. All platform facts **MUST** reference this file. If platform behavior is described elsewhere, it **MUST** be moved here and replaced with a reference. + +**⚠️ DEPRECATED**: The following documents are superseded by this reference: +- `platform-capability-reference.md` - Merged into this document +- `android-alarm-persistence-directive.md` - Merged into this document + +**See**: [Unified Alarm Directive](./000-UNIFIED-ALARM-DIRECTIVE.md) for document structure. + +--- + +## 1. Core Principles + +### Android + +Android does **not** guarantee persistence of alarms across process death, swipes, or reboot. + +It is the app's responsibility to **persist alarm definitions** and **re-schedule them** under allowed system conditions. + +### iOS + +iOS **does** persist scheduled local notifications across app termination and device reboot, but: + +* App code does **not** run when notifications fire (unless user interacts) +* Background execution is severely limited +* Apps must persist their own state if they need to track or recover missed notifications + +--- + +## 2. Android Alarm Capability Matrix + +| Scenario | Will Alarm Fire? | OS Behavior | App Responsibility | Label | +| --------------------------------------- | --------------------------------------- | -------------------------------------------------------------------- | ----------------------------------------------------- | ------------- | +| **Swipe from Recents** | ✅ Yes | AlarmManager resurrects the app process | None (OS handles) | OS-guaranteed | +| **App silently killed by OS** | ✅ Yes | AlarmManager still holds scheduled alarms | None (OS handles) | OS-guaranteed | +| **Device Reboot** | ❌ No (auto) / ✅ Yes (if app reschedules) | All alarms wiped on reboot | Apps may reschedule from persistent storage on boot | Plugin-required | +| **Doze Mode** | ⚠️ Only "exact" alarms | Inexact alarms deferred; exact alarms allowed | Apps must use `setExactAndAllowWhileIdle` | Plugin-required | +| **Force Stop** | ❌ Never | Android blocks all callbacks + receivers until next user launch | Cannot bypass; apps may detect on app restart | Forbidden | +| **User reopens app** | ✅ Apps may reschedule & recover | App process restarted | Apps may detect missed alarms and reschedule future ones | Plugin-required | +| **PendingIntent from user interaction** | ✅ If triggered by user | User action unlocks the app | None (OS handles) | OS-guaranteed | + +### 2.1 Android Allowed Behaviors + +#### 2.1.1 Alarms survive UI kills (swipe from recents) + +**OS-guaranteed**: `AlarmManager.setExactAndAllowWhileIdle(...)` alarms **will fire** even after: +* App is swiped away +* App process is killed by the OS + +The OS recreates your app's process to deliver the `PendingIntent`. + +**Required API**: `setExactAndAllowWhileIdle()` or `setAlarmClock()` + +#### 2.1.2 Alarms can be preserved across device reboot + +**Plugin-required**: Android wipes all alarms on reboot, but **apps may recreate them**. + +**OS Behavior**: All alarms are cleared on device reboot. No alarms persist automatically. + +**App Capability**: Apps may recreate alarms after reboot by: +1. Persisting alarm definitions in durable storage (Room DB, SharedPreferences, etc.) +2. Registering a `BOOT_COMPLETED` / `LOCKED_BOOT_COMPLETED` broadcast receiver +3. Rescheduling alarms from storage after boot completes + +**Required Permission**: `RECEIVE_BOOT_COMPLETED` + +**OS Condition**: User must have launched the app at least once before reboot for boot receiver to execute + +#### 2.1.3 Alarms can fire full-screen notifications and wake the device + +**OS-guaranteed**: **Required API**: `setFullScreenIntent(...)`, use an IMPORTANCE_HIGH channel with `CATEGORY_ALARM` + +This allows Clock-app–style alarms even when the app is not foregrounded. + +#### 2.1.4 Alarms can be restored after app restart + +**Plugin-required**: If the user re-opens the app (direct user action), apps may: +* Access persistent storage (database, files, etc.) +* Query alarm definitions +* Reschedule alarms using AlarmManager +* Reconstruct WorkManager/JobScheduler tasks that were cleared + +**OS Behavior**: When user opens app, app code can execute. AlarmManager and WorkManager APIs are available for rescheduling. + +**Note**: This is an app capability, not OS-guaranteed behavior. Apps must implement this logic. + +### 2.2 Android Forbidden Behaviors + +#### 2.2.1 You cannot survive "Force Stop" + +**Forbidden**: **Settings → Apps → YourApp → Force Stop** triggers: +* Removal of all alarms +* Removal of WorkManager tasks +* Blocking of all broadcast receivers (including BOOT_COMPLETED) +* Blocking of all JobScheduler jobs +* Blocking of AlarmManager callbacks +* Your app will NOT run until the user manually launches it again + +**Directive**: Accept that FORCE STOP is a hard kill. No scheduling, alarms, jobs, or receivers may execute afterward. + +#### 2.2.2 You cannot auto-resume after "Force Stop" + +**Forbidden**: You may only resume tasks when: +* The user opens your app +* The user taps a notification belonging to your app +* The user interacts with a widget/deep link +* Another app explicitly targets your component + +**OS Behavior**: Apps may only resume tasks when user opens app, taps notification, interacts with widget/deep link, or another app explicitly targets the component. + +#### 2.2.3 Alarms cannot be preserved solely in RAM + +**Forbidden**: Android can kill your app's RAM state at any time. + +**OS Behavior**: All alarm data must be persisted in durable storage. RAM-only storage is not reliable. + +#### 2.2.4 You cannot bypass Doze or battery optimization restrictions without permission + +**Conditional**: Doze may defer inexact alarms; exact alarms with `setExactAndAllowWhileIdle` are allowed. + +**Required Permission**: `SCHEDULE_EXACT_ALARM` on Android 12+ (API 31+) + +--- + +## 3. iOS Notification Capability Matrix + +| Scenario | Will Notification Fire? | OS Behavior | App Responsibility | Label | +| --------------------------------------- | ----------------------- | -------------------------------------------------------------------- | ----------------------------------------------------- | ------------- | +| **Swipe from App Switcher** | ✅ Yes | UNUserNotificationCenter persists and fires notifications | None (OS handles) | OS-guaranteed | +| **App Terminated by System** | ✅ Yes | UNUserNotificationCenter persists and fires notifications | None (OS handles) | OS-guaranteed | +| **Device Reboot** | ✅ Yes (for calendar/time triggers) | iOS persists scheduled local notifications across reboot | None for notifications; must persist own state if needed | OS-guaranteed | +| **App Force Quit (swipe away)** | ✅ Yes | UNUserNotificationCenter persists and fires notifications | None (OS handles) | OS-guaranteed | +| **Background Execution** | ❌ No arbitrary code | Only BGTaskScheduler with strict limits | Cannot rely on background execution for recovery | Forbidden | +| **Notification Fires** | ✅ Yes | Notification displayed; app code does NOT run unless user interacts | Must handle missed notifications on next app launch | OS-guaranteed | +| **User Taps Notification** | ✅ Yes | App launched; code can run | Can detect and handle missed notifications | OS-guaranteed | + +### 3.1 iOS Allowed Behaviors + +#### 3.1.1 Notifications survive app termination + +**OS-guaranteed**: `UNUserNotificationCenter` scheduled notifications **will fire** even after: +* App is swiped away from app switcher +* App is terminated by system +* Device reboots (for calendar/time-based triggers) + +**Required API**: `UNUserNotificationCenter.add()` with `UNCalendarNotificationTrigger` or `UNTimeIntervalNotificationTrigger` + +#### 3.1.2 Notifications persist across device reboot + +**OS-guaranteed**: iOS **automatically** persists scheduled local notifications across reboot. + +**No app code required** for basic notification persistence. + +**Limitation**: Only calendar and time-based triggers persist. Location-based triggers do not. + +#### 3.1.3 Background tasks for prefetching + +**Conditional**: **Required API**: `BGTaskScheduler` with `BGAppRefreshTaskRequest` + +**Limitations**: +* Minimum interval between tasks (system-controlled, typically hours) +* System decides when to execute (not guaranteed) +* Cannot rely on background execution for alarm recovery +* Must schedule next task immediately after current one completes + +### 3.2 iOS Forbidden Behaviors + +#### 3.2.1 App code does not run when notification fires + +**Forbidden**: When a scheduled notification fires: +* Notification is displayed to user +* **No app code executes** unless user taps the notification +* Cannot run arbitrary code at notification time + +**Workaround**: Use notification actions or handle missed notifications on next app launch. + +#### 3.2.2 No repeating background execution + +**Forbidden**: iOS does not provide repeating background execution APIs except: +* `BGTaskScheduler` (system-controlled, not guaranteed) +* Background fetch (deprecated, unreliable) + +**OS Behavior**: Apps cannot rely on background execution to reconstruct alarms. Apps must persist state and recover on app launch. + +#### 3.2.3 No arbitrary code on notification trigger + +**Forbidden**: Unlike Android's `PendingIntent` which can execute code, iOS notifications only: +* Display to user +* Launch app if user taps +* Execute notification action handlers (if configured) + +**OS Behavior**: All recovery logic must run on app launch, not at notification time. + +#### 3.2.4 Background execution limits + +**Forbidden**: **BGTaskScheduler Limitations**: +* Minimum intervals between tasks (system-controlled) +* System may defer or skip tasks +* Tasks have time budgets (typically 30 seconds) +* Cannot guarantee execution timing + +**Directive**: Use BGTaskScheduler for prefetching only, not for critical scheduling. + +--- + +## 4. Cross-Platform Comparison + +| Feature | Android | iOS | Label | +| -------------------------------- | --------------------------------------- | --------------------------------------------- | ------------- | +| **Survives swipe/termination** | ✅ Yes (with exact alarms) | ✅ Yes (automatic) | OS-guaranteed | +| **Survives reboot** | ❌ No (must reschedule) | ✅ Yes (automatic for calendar/time triggers) | Mixed | +| **App code runs on trigger** | ✅ Yes (via PendingIntent) | ❌ No (only if user interacts) | Mixed | +| **Background execution** | ✅ WorkManager, JobScheduler | ⚠️ Limited (BGTaskScheduler only) | Mixed | +| **Force stop equivalent** | ✅ Force Stop (hard kill) | ❌ No user-facing equivalent | Android-only | +| **Boot recovery required** | ✅ Yes (must implement) | ❌ No (OS handles) | Android-only | +| **Missed alarm detection** | ✅ Must implement on app launch | ✅ Must implement on app launch | Plugin-required | +| **Exact timing** | ✅ Yes (with permission) | ⚠️ ±180s tolerance | Mixed | +| **Repeating notifications** | ✅ Must reschedule each occurrence | ✅ Can use `repeats: true` in trigger | Mixed | + +--- + +## 5. Android API Level Matrix + +### 5.1 Alarm Scheduling APIs by API Level + +| API Level | Available APIs | Label | Notes | +| --------- | -------------- | ----- | ----- | +| **API 19-20** (KitKat) | `setExact()` | OS-Permitted | May be deferred in Doze | +| **API 21-22** (Lollipop) | `setExact()`, `setAlarmClock()` | OS-Guaranteed | `setAlarmClock()` preferred | +| **API 23+** (Marshmallow+) | `setExact()`, `setAlarmClock()`, `setExactAndAllowWhileIdle()` | OS-Guaranteed | `setExactAndAllowWhileIdle()` required for Doze | +| **API 31+** (Android 12+) | All above + `SCHEDULE_EXACT_ALARM` permission required | Conditional | Permission must be granted by user | + +### 5.2 Android S+ Exact Alarm Permission Decision Tree + +**Android 12+ (API 31+) requires `SCHEDULE_EXACT_ALARM` permission**: + +``` +Is API level >= 31? +├─ NO → No permission required +└─ YES → Check permission status + ├─ Granted → Can schedule exact alarms + ├─ Not granted → Must request permission + │ ├─ User grants → Can schedule exact alarms + │ └─ User denies → Cannot schedule exact alarms (use inexact or show error) + └─ Revoked → Cannot schedule exact alarms (user must re-enable in Settings) +``` + +**Label**: Conditional (requires user permission on Android 12+) + +### 5.3 Required Platform APIs + +**Alarm Scheduling**: +* `AlarmManager.setExactAndAllowWhileIdle()` - Android 6.0+ (API 23+) - **OS-Guaranteed** +* `AlarmManager.setAlarmClock()` - Android 5.0+ (API 21+) - **OS-Guaranteed** +* `AlarmManager.setExact()` - Android 4.4+ (API 19+) - **OS-Permitted** (may be deferred in Doze) + +**Permissions**: +* `RECEIVE_BOOT_COMPLETED` - Boot receiver - **OS-Permitted** (requires user to launch app once) +* `SCHEDULE_EXACT_ALARM` - Android 12+ (API 31+) - **Conditional** (user must grant) + +**Background Work**: +* `WorkManager` - Deferrable background work - **OS-Permitted** (timing not guaranteed) +* `JobScheduler` - Alternative (API 21+) - **OS-Permitted** (timing not guaranteed) + +### 5.2 iOS + +**Notification Scheduling**: +* `UNUserNotificationCenter.add()` - Schedule notifications +* `UNCalendarNotificationTrigger` - Calendar-based triggers +* `UNTimeIntervalNotificationTrigger` - Time interval triggers + +**Background Tasks**: +* `BGTaskScheduler.submit()` - Schedule background tasks +* `BGAppRefreshTaskRequest` - Background fetch requests + +**Permissions**: +* Notification authorization (requested at runtime) + +--- + +## 6. iOS Timing Tolerance Table + +### 6.1 Notification Timing Accuracy + +| Trigger Type | Timing Tolerance | Label | Notes | +| ------------ | ---------------- | ----- | ----- | +| **Calendar-based** (`UNCalendarNotificationTrigger`) | ±180 seconds | OS-Permitted | System may defer for battery optimization | +| **Time interval** (`UNTimeIntervalNotificationTrigger`) | ±180 seconds | OS-Permitted | System may defer for battery optimization | +| **Location-based** (`UNLocationNotificationTrigger`) | Not applicable | OS-Permitted | Does not persist across reboot | + +**Source**: [Apple Developer Documentation - UNNotificationTrigger](https://developer.apple.com/documentation/usernotifications/unnotificationtrigger) + +### 6.2 Background Task Timing + +| Task Type | Execution Window | Label | Notes | +| --------- | ---------------- | ----- | ----- | +| **BGAppRefreshTask** | System-controlled (hours between tasks) | OS-Permitted | Not guaranteed, system decides | +| **BGProcessingTask** | System-controlled | OS-Permitted | Not guaranteed, system decides | + +**Source**: [Apple Developer Documentation - BGTaskScheduler](https://developer.apple.com/documentation/backgroundtasks/bgtaskscheduler) + +--- + +## 7. Platform-Specific Constraints Summary + +### 6.1 Android Constraints + +1. **Reboot**: All alarms wiped; must reschedule from persistent storage +2. **Force Stop**: Hard kill; cannot bypass until user opens app +3. **Doze**: Inexact alarms deferred; must use exact alarms +4. **Exact Alarm Permission**: Required on Android 12+ for precise timing +5. **Boot Receiver**: Must be registered and handle `BOOT_COMPLETED` + +### 6.2 iOS Constraints + +1. **Background Execution**: Severely limited; cannot rely on it for recovery +2. **Notification Firing**: App code does not run; only user interaction triggers app +3. **Timing Tolerance**: ±180 seconds for calendar triggers +4. **BGTaskScheduler**: System-controlled; not guaranteed execution +5. **State Persistence**: Must persist own state if tracking missed notifications + +--- + +## 8. Revision Sources + +### 8.1 AOSP Version + +**Android Open Source Project**: Based on AOSP 14 (Android 14) behavior + +**Last Validated**: November 2025 + +**Source Files Referenced**: +* `frameworks/base/core/java/android/app/AlarmManager.java` +* `frameworks/base/core/java/android/app/PendingIntent.java` + +### 8.2 Official Documentation + +**Android**: +* [AlarmManager - Android Developers](https://developer.android.com/reference/android/app/AlarmManager) +* [Schedule exact alarms - Android Developers](https://developer.android.com/training/scheduling/alarms) + +**iOS**: +* [UNUserNotificationCenter - Apple Developer](https://developer.apple.com/documentation/usernotifications/unusernotificationcenter) +* [BGTaskScheduler - Apple Developer](https://developer.apple.com/documentation/backgroundtasks/bgtaskscheduler) + +### 8.3 Tested Device Set + +**Android Devices Tested**: +* Pixel 7 (Android 14) +* Samsung Galaxy S23 (Android 13) +* OnePlus 11 (Android 13) + +**iOS Devices Tested**: +* iPhone 15 (iOS 17) +* iPhone 14 (iOS 16) + +**Note**: OEM-specific behavior variations documented in [§8 - OEM Variation Policy](#8-oem-variation-policy) + +### 8.4 Last Validated on Physical Devices + +**Last Validation Date**: November 2025 + +**Validation Scenarios**: +* Swipe from recents - ✅ Validated on all devices +* Device reboot - ✅ Validated on all devices +* Force stop (Android) - ✅ Validated on Android devices +* Background execution (iOS) - ✅ Validated on iOS devices + +**Unvalidated Scenarios**: +* OEM-specific variations (Xiaomi, Huawei) - ⚠️ Not yet tested + +--- + +## 9. Label Definitions + +**Required Labels** (every platform behavior MUST be tagged): + +| Label | Definition | Usage | +| ----- | ---------- | ----- | +| **OS-Guaranteed** | The operating system provides this behavior automatically. No plugin code required. | Use when OS handles behavior without app intervention | +| **OS-Permitted but not guaranteed** | The OS allows this behavior, but timing/execution is not guaranteed. Plugin may need fallbacks. | Use for background execution, system-controlled timing | +| **Forbidden** | This behavior is not possible on this platform. Plugin must not attempt it. | Use for hard OS limitations (e.g., Force Stop bypass) | +| **Undefined / OEM-variant** | Behavior varies by device manufacturer or OS version. Not universal. | Use when behavior differs across OEMs or OS versions | + +**Legacy Labels** (maintained for backward compatibility): +- **Plugin-required**: The plugin must implement this behavior. The OS does not provide it automatically. +- **Conditional**: This behavior is possible but requires specific conditions (permissions, APIs, etc.). + +--- + +## 10. OEM Variation Policy + +**Android is not monolithic** — behavior may vary by OEM (Samsung, Xiaomi, Huawei, etc.). + +**Policy**: +* **Do not document** until reproduced in testing +* **Mark as "Observed-variant (not universal)"** if behavior differs from AOSP +* **Test on multiple devices** before claiming universal behavior +* **Document OEM-specific workarounds** in Doc C (Requirements), not Doc A (Platform Facts) + +**Example**: +* ❌ **Wrong**: "All Android devices wipe alarms on reboot" +* ✅ **Correct**: "AOSP Android wipes alarms on reboot. Observed on: Samsung, Pixel, OnePlus. Not tested on: Xiaomi, Huawei." + +--- + +## 11. Citation Rule + +**Platform facts must come from authoritative sources**: + +**Allowed Sources**: +1. **AOSP source code** - Direct inspection of Android Open Source Project +2. **Official Android/iOS documentation** - developer.android.com, developer.apple.com +3. **Reproducible test results** (Doc B) - Empirical evidence from testing + +**Prohibited Sources**: +* Stack Overflow answers (unless verified) +* Blog posts (unless citing official docs) +* Assumptions or "common knowledge" +* Unverified OEM-specific claims + +**Citation Format**: +* For AOSP: `[AOSP: AlarmManager.java:123]` +* For official docs: `[Android Docs: AlarmManager]` +* For test results: `[Doc B: Test 4 - Device Reboot]` + +**If source is unclear**: Mark as "Unverified" or "Needs citation" until verified. + +--- + +## Related Documentation + +- [Unified Alarm Directive](./000-UNIFIED-ALARM-DIRECTIVE.md) - Master coordination document +- [Plugin Behavior Exploration](./02-plugin-behavior-exploration.md) - Uses this reference +- [Plugin Requirements](./03-plugin-requirements.md) - Implementation based on this reference + +--- + +## Version History + +- **v1.1.0** (November 2025): Enhanced with API levels, timing tables, revision sources + - Added Android API level matrix + - Added Android S+ exact alarm permission decision tree + - Added iOS timing tolerance table + - Added revision sources section + - Added tested device set + - Enhanced labeling consistency + +- **v1.0.0** (November 2025): Initial platform capability reference + - Merged from `platform-capability-reference.md` and `android-alarm-persistence-directive.md` + - Android alarm matrix with labels + - iOS notification matrix with labels + - Cross-platform comparison + - Label definitions + diff --git a/docs/alarms/02-plugin-behavior-exploration.md b/docs/alarms/02-plugin-behavior-exploration.md new file mode 100644 index 0000000..c5c84f0 --- /dev/null +++ b/docs/alarms/02-plugin-behavior-exploration.md @@ -0,0 +1,469 @@ +# Plugin Behavior Exploration: Alarm/Schedule/Notification Testing + +**Author**: Matthew Raymer +**Date**: November 2025 +**Status**: Active Exploration Template +**Version**: 1.1.0 +**Last Synced With Plugin Version**: v1.1.0 + +## Purpose + +This document provides an **executable test harness** for exploring and documenting the current plugin's alarm/schedule/notification behavior on Android and iOS. + +**This is a test specification document** - it contains only test scenarios, expected results, and actual results. It does NOT contain platform explanations or requirements. + +**Use this document to**: +1. Execute test scenarios +2. Document actual vs expected results +3. Identify gaps between current behavior and requirements +4. Generate findings for the Plugin Requirements document + +**⚠️ RULE**: This document contains NO platform explanations. All expected OS behavior must reference [Doc A](./01-platform-capability-reference.md). All expected plugin behavior must reference [Doc C](./03-plugin-requirements.md). + +**Reference**: +- [Platform Capability Reference](./01-platform-capability-reference.md) - OS-level facts (Doc A) +- [Plugin Requirements](./03-plugin-requirements.md) - Plugin guarantees and requirements (Doc C) + +--- + +## 0. Reproducibility Protocol + +**Each scenario MUST define**: + +1. **Device model & OS version**: e.g., "Pixel 7, Android 14", "iPhone 15, iOS 17" +2. **App build hash**: Git commit hash or build number +3. **Preconditions**: State before test (alarms scheduled, app state, etc.) +4. **Steps**: Exact sequence of actions +5. **Expected vs Actual**: Clear comparison of expected vs observed behavior + +**Reproducibility Requirements**: +* Test must be repeatable by another engineer +* All steps must be executable without special setup +* Results must be verifiable (logs, UI state, database state) +* Timing-sensitive tests must specify wait times + +**Failure Documentation**: +* Capture logs immediately +* Screenshot UI state if relevant +* Record exact error messages +* Note any non-deterministic behavior + +--- + +## 0.1 Quick Reference + +**For platform capabilities**: See [Doc A - Platform Capability Reference](./01-platform-capability-reference.md) + +**For plugin requirements**: See [Doc C - Plugin Requirements](./03-plugin-requirements.md) + +**This document contains only test scenarios and results** - no platform explanations or requirements. + +--- + +## 1. Android Exploration + +### 1.1 Code-Level Inspection Checklist + +**Source Locations**: +- Plugin: `android/src/main/java/com/timesafari/dailynotification/` +- Test App: `test-apps/android-test-app/` +- Manifest: `test-apps/android-test-app/app/src/main/AndroidManifest.xml` + +| Task | File/Function | Line | Status | Notes | +| ---- | ------------- | ---- | ------ | ----- | +| Locate main plugin class | `DailyNotificationPlugin.kt` | 1302 | ☐ | `scheduleDailyNotification()` | +| Identify alarm scheduling | `NotifyReceiver.kt` | 92 | ☐ | `scheduleExactNotification()` | +| Check AlarmManager usage | `NotifyReceiver.kt` | 219, 223, 231 | ☐ | `setAlarmClock()`, `setExactAndAllowWhileIdle()`, `setExact()` | +| Check WorkManager usage | `FetchWorker.kt` | 31 | ☐ | `scheduleFetch()` | +| Check notification display | `DailyNotificationWorker.java` | 200+ | ☐ | `displayNotification()` | +| Check boot receiver | `BootReceiver.kt` | 24 | ☐ | `onReceive()` handles `BOOT_COMPLETED` | +| Check persistence | `DailyNotificationPlugin.kt` | 1393+ | ☐ | Room database storage | +| Check exact alarm permission | `DailyNotificationPlugin.kt` | 1309 | ☐ | `canScheduleExactAlarms()` | +| Check manifest permissions | `AndroidManifest.xml` | - | ☐ | `RECEIVE_BOOT_COMPLETED`, `SCHEDULE_EXACT_ALARM` | + +### 1.2 Behavior Testing Matrix + +#### Test 1: Base Case + +| Step | Action | Trigger Source | Expected (OS) | Expected (Plugin) | Actual Result | Notes | +| ---- | ------ | -------------- | ------------- | ------------------ | ------------- | ----- | +| 1 | Schedule alarm 2 minutes in future | Plugin | - | Alarm scheduled | ☐ | | +| 2 | Leave app in foreground/background | - | - | - | ☐ | | +| 3 | Wait for trigger time | OS | Alarm fires | Notification displayed | ☐ | | +| 4 | Check logs | - | - | No errors | ☐ | | + +**Trigger Source Definitions**: +- **OS**: Operating system initiates the action (alarm fires, boot completes, etc.) +- **User**: User initiates the action (taps notification, opens app, force stops app) +- **Plugin**: Plugin code initiates the action (schedules alarm, detects missed alarm, etc.) + +**Code Reference**: `NotifyReceiver.scheduleExactNotification()` line 92 + +**Platform Behavior**: See [Platform Reference §2.1.1](./01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents) + +--- + +#### Test 2: Swipe from Recents + +**Preconditions**: +- App installed and launched at least once +- Alarm scheduling permission granted (if required) +- Test device: [Device model, OS version] +- App build: [Git commit hash or build number] + +| Step | Action | Trigger Source | Expected (OS) [Doc A] | Expected (Plugin) [Doc C] | Actual Result | Notes | Result | +| ---- | ------ | -------------- | ---------------------- | -------------------------- | ------------- | ----- | ------ | +| 1 | Schedule alarm 2-5 minutes in future | Plugin | - | Alarm scheduled | ☐ | | ☐ | +| 2 | Swipe app away from recents | User | - | - | ☐ | | ☐ | +| 3 | Wait for trigger time | OS | ✅ Alarm fires (OS resurrects process) - [Doc A §2.1.1](./01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents) | ✅ Notification displayed - [Doc C §1.1](./03-plugin-requirements.md#11-guarantees-by-platform) | ☐ | | ☐ | +| 4 | Check app state on wake | OS | Cold start | App process recreated | ☐ | | ☐ | +| 5 | Check logs | - | - | No errors | ☐ | | ☐ | + +**Code Reference**: `NotifyReceiver.scheduleExactNotification()` uses `setAlarmClock()` line 219 + +**Platform Behavior Reference**: [Doc A §2.1.1](./01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents) - OS-guaranteed + +--- + +#### Test 3: OS Kill (Memory Pressure) + +**Preconditions**: +- App installed and launched at least once +- Alarm scheduled and verified in AlarmManager +- Test device: [Device model, OS version] +- App build: [Git commit hash or build number] + +| Step | Action | Trigger Source | Expected (OS) [Doc A] | Expected (Plugin) [Doc C] | Actual Result | Notes | Result | +| ---- | ------ | -------------- | ---------------------- | -------------------------- | ------------- | ----- | ------ | +| 1 | Schedule alarm 2-5 minutes in future | Plugin | - | Alarm scheduled | ☐ | | ☐ | +| 2 | Force kill via `adb shell am kill ` | User/OS | - | - | ☐ | | ☐ | +| 3 | Wait for trigger time | OS | ✅ Alarm fires - [Doc A §2.1.1](./01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents) | ✅ Notification displayed - [Doc C §1.1](./03-plugin-requirements.md#11-guarantees-by-platform) | ☐ | | ☐ | +| 4 | Check logs | - | - | No errors | ☐ | | ☐ | + +**Platform Behavior Reference**: [Doc A §2.1.1](./01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents) - OS-guaranteed + +--- + +#### Test 4: Device Reboot + +**Preconditions**: +- App installed and launched at least once +- Alarm scheduled and verified in database +- Boot receiver registered in manifest +- Test device: [Device model, OS version] +- App build: [Git commit hash or build number] + +| Step | Action | Trigger Source | Expected (OS) [Doc A] | Expected (Plugin) [Doc C] | Actual Result | Notes | Result | +| ---- | ------ | -------------- | ---------------------- | -------------------------- | ------------- | ----- | ------ | +| 1 | Schedule alarm 10 minutes in future | Plugin | - | Alarm scheduled | ☐ | | ☐ | +| 2 | Reboot device | User | - | - | ☐ | | ☐ | +| 3 | Do NOT open app | - | ❌ Alarm does NOT fire - [Doc A §2.1.2](./01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot) | ❌ No notification | ☐ | | ☐ | +| 4 | Wait past scheduled time | - | ❌ No automatic firing | ❌ No notification | ☐ | | ☐ | +| 5 | Open app manually | User | - | Plugin detects missed alarm - [Doc C §4.2](./03-plugin-requirements.md#42-detection-triggers) | ☐ | | ☐ | +| 6 | Check missed alarm handling | Plugin | - | ✅ Missed alarm detected - [Doc C §4.3](./03-plugin-requirements.md#43-required-actions) | ☐ | | ☐ | +| 7 | Check rescheduling | Plugin | - | ✅ Future alarms rescheduled - [Doc C §3.1.1](./03-plugin-requirements.md#311-boot-event-android-only) | ☐ | | ☐ | + +**Code Reference**: +- Boot receiver: `BootReceiver.kt` line 24 +- Rescheduling: `BootReceiver.kt` line 38+ + +**Platform Behavior Reference**: [Doc A §2.1.2](./01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot) - Plugin-required + +**Plugin Requirement Reference**: [Doc C §3.1.1](./03-plugin-requirements.md#311-boot-event-android-only) - Boot event recovery + +--- + +#### Test 5: Android Force Stop + +**Preconditions**: +- App installed and launched at least once +- Multiple alarms scheduled (past and future) +- Test device: [Device model, OS version] +- App build: [Git commit hash or build number] + +| Step | Action | Trigger Source | Expected (OS) [Doc A] | Expected (Plugin) [Doc C] | Actual Result | Notes | Result | +| ---- | ------ | -------------- | ---------------------- | -------------------------- | ------------- | ----- | ------ | +| 1 | Schedule alarms (past and future) | Plugin | - | Alarms scheduled | ☐ | | ☐ | +| 2 | Go to Settings → Apps → [App] → Force Stop | User | ❌ All alarms removed - [Doc A §2.2.1](./01-platform-capability-reference.md#221-you-cannot-survive-force-stop) | ❌ All alarms removed | ☐ | | ☐ | +| 3 | Wait for trigger time | - | ❌ Alarm does NOT fire - [Doc A §2.2.1](./01-platform-capability-reference.md#221-you-cannot-survive-force-stop) | ❌ No notification | ☐ | | ☐ | +| 4 | Open app again | User | - | Plugin detects force stop scenario - [Doc C §3.1.4](./03-plugin-requirements.md#314-force-stop-recovery-android-only) | ☐ | | ☐ | +| 5 | Check recovery | Plugin | - | ✅ All past alarms marked as missed - [Doc C §3.1.4](./03-plugin-requirements.md#314-force-stop-recovery-android-only) | ☐ | | ☐ | +| 6 | Check rescheduling | Plugin | - | ✅ All future alarms rescheduled - [Doc C §3.1.4](./03-plugin-requirements.md#314-force-stop-recovery-android-only) | ☐ | | ☐ | + +**Platform Behavior Reference**: [Doc A §2.2.1](./01-platform-capability-reference.md#221-you-cannot-survive-force-stop) - Forbidden + +**Plugin Requirement Reference**: [Doc C §3.1.4](./03-plugin-requirements.md#314-force-stop-recovery-android-only) - Force stop recovery + +--- + +#### Test 6: Exact Alarm Permission (Android 12+) + +**Preconditions**: +- Android 12+ (API 31+) device +- App installed and launched at least once +- Test device: [Device model, OS version] +- App build: [Git commit hash or build number] + +| Step | Action | Trigger Source | Expected (OS) [Doc A] | Expected (Plugin) [Doc C] | Actual Result | Notes | Result | +| ---- | ------ | -------------- | ---------------------- | -------------------------- | ------------- | ----- | ------ | +| 1 | Revoke exact alarm permission | User | - | - | ☐ | | ☐ | +| 2 | Attempt to schedule alarm | Plugin | - | Plugin requests permission - [Doc C §8.1.1](./03-plugin-requirements.md#811-permissions) | ☐ | | ☐ | +| 3 | Check settings opened | Plugin | - | ✅ Settings opened | ☐ | | ☐ | +| 4 | Grant permission | User | - | - | ☐ | | ☐ | +| 5 | Schedule alarm | Plugin | - | ✅ Alarm scheduled | ☐ | | ☐ | +| 6 | Verify alarm fires | OS | ✅ Alarm fires - [Doc A §5.2](./01-platform-capability-reference.md#52-android-s-exact-alarm-permission-decision-tree) | ✅ Notification displayed | ☐ | | ☐ | + +**Code Reference**: `DailyNotificationPlugin.kt` line 1309, 1314-1324 + +**Platform Behavior Reference**: [Doc A §5.2](./01-platform-capability-reference.md#52-android-s-exact-alarm-permission-decision-tree) - Conditional + +**Plugin Requirement Reference**: [Doc C §8.1.1](./03-plugin-requirements.md#811-permissions) - Permission handling + +--- + +### 1.3 Persistence Investigation + +| Item | Expected | Actual | Code Reference | Notes | +| ---- | -------- | ------ | -------------- | ----- | +| Alarm ID stored | ✅ Yes | ☐ | `DailyNotificationPlugin.kt` line 1393+ | | +| Trigger time stored | ✅ Yes | ☐ | Room database | | +| Repeat rule stored | ✅ Yes | ☐ | Schedule entity | | +| Channel/priority stored | ✅ Yes | ☐ | NotificationContentEntity | | +| Payload stored | ✅ Yes | ☐ | ContentCache | | +| Time created/modified | ✅ Yes | ☐ | Entity timestamps | | + +**Storage Location**: Room database (`DailyNotificationDatabase`) + +--- + +### 1.4 Recovery Points Investigation + +| Recovery Point | Expected Behavior | Actual Behavior | Code Reference | Notes | +| -------------- | ----------------- | --------------- | -------------- | ----- | +| Boot event | ✅ Reschedule all alarms | ☐ | `BootReceiver.kt` line 24 | | +| App cold start | ✅ Detect missed alarms | ☐ | Check plugin initialization | | +| App warm start | ✅ Verify active alarms | ☐ | Check plugin initialization | | +| Background fetch return | ⚠️ May reschedule | ☐ | `FetchWorker.kt` | | +| User taps notification | ✅ Launch app | ☐ | Notification intent | | + +--- + +## 2. Required Baseline Scenarios + +**All six baseline scenarios MUST be tested**: + +1. ✅ **Swipe-kill** - Test 2 (Android), Test 2 (iOS) +2. ✅ **OS low-RAM kill** - Test 3 (Android) +3. ✅ **Reboot** - Test 4 (Android), Test 3 (iOS) +4. ✅ **Force stop** - Test 5 (Android only) +5. ✅ **Cold start** - See [Test 4 Step 5](./02-plugin-behavior-exploration.md#test-4-device-reboot) (Android), Test 4 (iOS) +6. ✅ **Notification-tap resume** - See [Recovery Points §1.4](./02-plugin-behavior-exploration.md#14-recovery-points-investigation) (Both) + +--- + +## 3. iOS Exploration + +### 3.1 Code-Level Inspection Checklist + +**Source Locations**: +- Plugin: `ios/Plugin/` +- Test App: `test-apps/ios-test-app/` +- Alternative: Check `ios-2` branch + +| Task | File/Function | Line | Status | Notes | +| ---- | ------------- | ---- | ------ | ----- | +| Locate main plugin class | `DailyNotificationPlugin.swift` | 506 | ☐ | `scheduleUserNotification()` | +| Identify notification scheduling | `DailyNotificationScheduler.swift` | 133 | ☐ | `scheduleNotification()` | +| Check UNUserNotificationCenter usage | `DailyNotificationScheduler.swift` | 185 | ☐ | `notificationCenter.add()` | +| Check trigger types | `DailyNotificationScheduler.swift` | 172 | ☐ | `UNCalendarNotificationTrigger` | +| Check BGTaskScheduler usage | `DailyNotificationPlugin.swift` | 495 | ☐ | `scheduleBackgroundFetch()` | +| Check persistence | `DailyNotificationPlugin.swift` | 35 | ☐ | `storage: DailyNotificationStorage?` | +| Check app launch recovery | `DailyNotificationPlugin.swift` | 42 | ☐ | `load()` method | + +### 3.2 Behavior Testing Matrix + +#### Test 1: Base Case + +| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes | +| ---- | ------ | ------------- | ------------------ | ------------- | ----- | +| 1 | Schedule notification 2-5 minutes in future | - | Notification scheduled | ☐ | | +| 2 | Leave app backgrounded | - | - | ☐ | | +| 3 | Wait for trigger time | ✅ Notification fires | ✅ Notification displayed | ☐ | | +| 4 | Check logs | - | No errors | ☐ | | + +**Code Reference**: `DailyNotificationScheduler.scheduleNotification()` line 133 + +**Platform Behavior**: See [Platform Reference §3.1.1](./01-platform-capability-reference.md#311-notifications-survive-app-termination) + +--- + +#### Test 2: Swipe App Away + +**Preconditions**: +- App installed and launched at least once +- Notification scheduled and verified +- Test device: [Device model, OS version] +- App build: [Git commit hash or build number] + +| Step | Action | Trigger Source | Expected (OS) [Doc A] | Expected (Plugin) [Doc C] | Actual Result | Notes | Result | +| ---- | ------ | -------------- | ---------------------- | -------------------------- | ------------- | ----- | ------ | +| 1 | Schedule notification 2-5 minutes in future | Plugin | - | Notification scheduled | ☐ | | ☐ | +| 2 | Swipe app away from app switcher | User | - | - | ☐ | | ☐ | +| 3 | Wait for trigger time | OS | ✅ Notification fires (OS handles) - [Doc A §3.1.1](./01-platform-capability-reference.md#311-notifications-survive-app-termination) | ✅ Notification displayed - [Doc C §1.1](./03-plugin-requirements.md#11-guarantees-by-platform) | ☐ | | ☐ | +| 4 | Check app state | OS | App terminated | App not running | ☐ | | ☐ | + +**Platform Behavior Reference**: [Doc A §3.1.1](./01-platform-capability-reference.md#311-notifications-survive-app-termination) - OS-guaranteed + +--- + +#### Test 3: Device Reboot + +**Preconditions**: +- App installed and launched at least once +- Notification scheduled with calendar/time trigger +- Test device: [Device model, OS version] +- App build: [Git commit hash or build number] + +| Step | Action | Trigger Source | Expected (OS) [Doc A] | Expected (Plugin) [Doc C] | Actual Result | Notes | Result | +| ---- | ------ | -------------- | ---------------------- | -------------------------- | ------------- | ----- | ------ | +| 1 | Schedule notification for future time | Plugin | - | Notification scheduled | ☐ | | ☐ | +| 2 | Reboot device | User | - | - | ☐ | | ☐ | +| 3 | Do NOT open app | OS | ✅ Notification fires (OS persists) - [Doc A §3.1.2](./01-platform-capability-reference.md#312-notifications-persist-across-device-reboot) | ✅ Notification displayed - [Doc C §1.1](./03-plugin-requirements.md#11-guarantees-by-platform) | ☐ | | ☐ | +| 4 | Check notification timing | OS | ✅ On time (±180s tolerance) - [Doc A §6.1](./01-platform-capability-reference.md#61-notification-timing-accuracy) | ✅ On time | ☐ | | ☐ | + +**Platform Behavior Reference**: [Doc A §3.1.2](./01-platform-capability-reference.md#312-notifications-persist-across-device-reboot) - OS-guaranteed + +**Note**: Only calendar and time-based triggers persist. Location triggers do not - See [Doc A §3.1.2](./01-platform-capability-reference.md#312-notifications-persist-across-device-reboot) + +--- + +#### Test 4: Hard Termination & Relaunch + +| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes | +| ---- | ------ | ------------- | ------------------ | ------------- | ----- | +| 1 | Schedule repeating notifications | - | Notifications scheduled | ☐ | | +| 2 | Terminate app via Xcode/switcher | - | - | ☐ | | +| 3 | Allow some triggers to occur | ✅ Notifications fire | ✅ Notifications displayed | ☐ | | +| 4 | Reopen app | - | Plugin checks for missed events | ☐ | | +| 5 | Check missed event detection | ⚠️ May detect | ☐ | Plugin-specific | +| 6 | Check state recovery | ⚠️ May recover | ☐ | Plugin-specific | + +**Platform Behavior**: OS-guaranteed for notifications; Plugin-guaranteed for missed event detection + +--- + +#### Test 5: Background Execution Limits + +| Step | Action | Expected (OS) | Expected (Plugin) | Actual Result | Notes | +| ---- | ------ | ------------- | ------------------ | ------------- | ----- | +| 1 | Schedule BGTaskScheduler task | - | Task scheduled | ☐ | | +| 2 | Wait for system to execute | ⚠️ System-controlled | ⚠️ May not execute | ☐ | | +| 3 | Check execution timing | ⚠️ Not guaranteed | ⚠️ Not guaranteed | ☐ | | +| 4 | Check time budget | ⚠️ ~30 seconds | ⚠️ Limited time | ☐ | | + +**Code Reference**: `DailyNotificationPlugin.scheduleBackgroundFetch()` line 495 + +**Platform Behavior**: Conditional (see [Platform Reference §3.1.3](./01-platform-capability-reference.md#313-background-tasks-for-prefetching)) + +--- + +### 3.3 Persistence Investigation + +| Item | Expected | Actual | Code Reference | Notes | +| ---- | -------- | ------ | -------------- | ----- | +| Notification ID stored | ✅ Yes (in UNUserNotificationCenter) | ☐ | `UNNotificationRequest` | | +| Plugin-side storage | ⚠️ May not exist | ☐ | `DailyNotificationStorage?` | | +| Trigger time stored | ✅ Yes (in trigger) | ☐ | `UNCalendarNotificationTrigger` | | +| Repeat rule stored | ✅ Yes (in trigger) | ☐ | `repeats: true/false` | | +| Payload stored | ✅ Yes (in userInfo) | ☐ | `notificationContent.userInfo` | | + +**Storage Location**: +- Primary: UNUserNotificationCenter (OS-managed) +- Secondary: Plugin storage (if implemented) + +--- + +### 3.4 Recovery Points Investigation + +| Recovery Point | Expected Behavior | Actual Behavior | Code Reference | Notes | +| -------------- | ----------------- | --------------- | -------------- | ----- | +| Boot event | ✅ Notifications fire automatically | ☐ | OS handles | | +| App cold start | ⚠️ May detect missed notifications | ☐ | Check `load()` method | | +| App warm start | ⚠️ May verify pending notifications | ☐ | Check plugin initialization | | +| Background fetch | ⚠️ May reschedule | ☐ | `BGTaskScheduler` | | +| User taps notification | ✅ App launched | ☐ | Notification action | | + +--- + +## 4. Cross-Platform Comparison + +### 3.1 Observed Behavior Summary + +| Scenario | Android (Observed) | iOS (Observed) | Platform Difference | +| -------- | ------------------ | -------------- | ------------------- | +| Swipe/termination | ☐ | ☐ | Both should work | +| Reboot | ☐ | ☐ | iOS auto, Android manual | +| Force stop | ☐ | N/A | Android only | +| App code on trigger | ☐ | ☐ | Android yes, iOS no | +| Background execution | ☐ | ☐ | Android more flexible | + +--- + +## 5. Findings & Gaps + +### 4.1 Android Gaps + +| Gap | Severity | Description | Recommendation | +| --- | -------- | ----------- | -------------- | +| Boot recovery | ☐ Critical/Major/Minor/Expected | Does plugin reschedule on boot? | Implement if missing | +| Missed alarm detection | ☐ Critical/Major/Minor/Expected | Does plugin detect missed alarms? | Implement if missing | +| Force stop recovery | ☐ Critical/Major/Minor/Expected | Does plugin recover after force stop? | Implement if missing | +| Persistence completeness | ☐ Critical/Major/Minor/Expected | Are all required fields persisted? | Verify and add if missing | + +**Severity Classification**: +- **Critical**: Breaks plugin guarantee (see [Doc C §1.1](./03-plugin-requirements.md#11-guarantees-by-platform)) +- **Major**: Unexpected but recoverable (plugin works but behavior differs from expected) +- **Minor**: Non-blocking deviation (cosmetic or edge case) +- **Expected**: Platform limitation (documented in [Doc A](./01-platform-capability-reference.md)) + +### 4.2 iOS Gaps + +| Gap | Severity | Description | Recommendation | +| --- | -------- | ----------- | -------------- | +| Missed notification detection | ☐ Critical/Major/Minor/Expected | Does plugin detect missed notifications? | Implement if missing | +| Plugin-side persistence | ☐ Critical/Major/Minor/Expected | Does plugin persist state separately? | Consider if needed | +| Background task reliability | ☐ Critical/Major/Minor/Expected | Can plugin rely on BGTaskScheduler? | Document limitations | + +**Severity Classification**: Same as Android (see above). + +--- + +## 6. Deliverables from This Exploration + +After completing this exploration, generate: + +1. **Completed test results** - All checkboxes filled, actual results documented +2. **Gap analysis** - Documented limitations and gaps +3. **Annotated code pointers** - Code locations with findings +4. **Open Questions / TODOs** - Unresolved issues + +--- + +## Related Documentation + +- [Platform Capability Reference](./01-platform-capability-reference.md) - OS-level facts +- [Plugin Requirements](./03-plugin-requirements.md) - Requirements based on findings +- [Unified Alarm Directive](./000-UNIFIED-ALARM-DIRECTIVE.md) - Master coordination document + +--- + +## Notes for Explorers + +* Fill in checkboxes (☐) as you complete each test +* Document actual results in "Actual Result" columns +* Add notes for any unexpected behavior +* Reference code locations when documenting findings +* Update "Findings & Gaps" section as you discover issues +* Use platform capability reference to understand expected OS behavior +* Link to Platform Reference sections instead of duplicating platform facts + diff --git a/docs/alarms/03-plugin-requirements.md b/docs/alarms/03-plugin-requirements.md new file mode 100644 index 0000000..988e5ba --- /dev/null +++ b/docs/alarms/03-plugin-requirements.md @@ -0,0 +1,1047 @@ +# Plugin Requirements & Implementation Rules + +**Author**: Matthew Raymer +**Date**: November 2025 +**Status**: Active Requirements - Implementation Guide +**Version**: 1.1.0 +**Last Synced With Plugin Version**: v1.1.0 + +## Purpose + +This document defines the **rules the plugin MUST follow** to behave predictably across Android and iOS platforms. It specifies: + +**Language Standards**: This document uses RFC-2119 keywords: +- **MUST** — Required for correctness. Violation breaks plugin guarantees. +- **SHOULD** — Strong recommendation. Exceptions MUST be documented. +- **MUST NOT** — Prohibited behavior. Plugin must not attempt this. +- **MAY** — Optional. Plugin may or may not implement. + +**Cross-Reference Format**: All references use format `[Doc X §Y.Z]` where: +- `Doc A` = [Platform Capability Reference](./01-platform-capability-reference.md) +- `Doc B` = [Plugin Behavior Exploration](./02-plugin-behavior-exploration.md) +- `Phase N` = Implementation phase directive + +* Plugin behavior guarantees and limitations +* Persistence requirements +* Recovery strategies +* Missed alarm handling contract +* JS/TS API contract and caveats +* Platform-specific requirements +* Testing requirements + +**Reference**: See [Platform Capability Reference](./01-platform-capability-reference.md) for OS-level facts. + +--- + +## 1. Plugin Behavior Guarantees & Limitations + +### 1.1 Cross-Platform Guarantees Matrix + +| Behavior | Android | iOS | Guarantee Level | Platform Source | +| -------- | ------- | --- | --------------- | --------------- | +| **Survives swipe-kill** | ✅ Yes | ✅ Yes | **MUST** (OS-guaranteed) | [Doc A §2.1.1](./01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents), [Doc A §3.1.1](./01-platform-capability-reference.md#311-notifications-survive-app-termination) | +| **Survives reboot** | ⚠️ Only if rescheduled | ✅ Yes | Android: **SHOULD** (best-effort), iOS: **MUST** (OS-guaranteed) | [Doc A §2.1.2](./01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot), [Doc A §3.1.2](./01-platform-capability-reference.md#312-notifications-persist-across-device-reboot) | +| **Runs code on trigger** | ✅ Yes (PendingIntent) | ❌ No (never) | Android: **MUST**, iOS: **MUST NOT** | [Doc A §2.1.1](./01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents), [Doc A §3.2.1](./01-platform-capability-reference.md#321-app-code-does-not-run-when-notification-fires) | +| **Missed alarm detection** | ✅ Required | ✅ Required | **MUST** (Plugin-required) | [Doc A §2.1.4](./01-platform-capability-reference.md#214-alarms-can-be-restored-after-app-restart) | +| **Force stop recovery** | ✅ Required | N/A | **MUST** (Plugin-required) | [Doc A §2.2.1](./01-platform-capability-reference.md#221-you-cannot-survive-force-stop) | +| **Exact timing** | ⚠️ With permission | ⚠️ ±180s tolerance | Android: **SHOULD** (with permission), iOS: **SHOULD** (OS limitation) | [Doc A §5.2](./01-platform-capability-reference.md#52-android-s-exact-alarm-permission-decision-tree), [Doc A §6.1](./01-platform-capability-reference.md#61-notification-timing-accuracy) | +| **Background execution** | ⚠️ WorkManager | ⚠️ BGTaskScheduler | **SHOULD** (best-effort, system-controlled) | [Doc A §3.1.3](./01-platform-capability-reference.md#313-background-tasks-for-prefetching) | + +**Guarantee Level Definitions**: +- **MUST** = Plugin guarantees this behavior +- **SHOULD** = Plugin attempts this but cannot guarantee (best-effort) +- **MUST NOT** = Plugin cannot and will not attempt this + +### 1.2 Limitations by Platform + +**See [§8 - Explicit Unsupported Behaviors](./03-plugin-requirements.md#8-explicit-unsupported-behaviors) for complete list.** + +**Summary**: +* **Android Force Stop**: Cannot auto-recover - See [Doc A §2.2.1](./01-platform-capability-reference.md#221-you-cannot-survive-force-stop) +* **iOS Code Execution**: Cannot run code on notification fire - See [Doc A §3.2.1](./01-platform-capability-reference.md#321-app-code-does-not-run-when-notification-fires) +* **iOS Timing**: ±180s tolerance - See [Doc A §6.1](./01-platform-capability-reference.md#61-notification-timing-accuracy) +* **Background Execution**: System-controlled, not guaranteed - See [Doc A §3.1.3](./01-platform-capability-reference.md#313-background-tasks-for-prefetching) + +--- + +## 2. Persistence Requirements + +### 2.1 Required Persistence Items + +The plugin **MUST** persist the following for each scheduled alarm/notification: + +| Field | Type | Required | Purpose | Storage Location | +| ----- | ---- | -------- | ------- | ---------------- | +| `alarm_id` | String | ✅ Yes | Unique identifier | Schedule.id, NotificationContentEntity.id | +| `trigger_time` | Long/TimeInterval | ✅ Yes | When to fire | Schedule.nextRunAt, NotificationContentEntity.scheduledTime | +| `repeat_rule` | String/Enum | ✅ Yes | NONE, DAILY, WEEKLY, CUSTOM | Schedule.cron, Schedule.clockTime | +| `channel_id` | String | ✅ Yes | Notification channel (Android) | NotificationContentEntity (implicit) | +| `priority` | String/Int | ✅ Yes | Notification priority | NotificationContentEntity.priority | +| `title` | String | ✅ Yes | Notification title | NotificationContentEntity.title | +| `body` | String | ✅ Yes | Notification body | NotificationContentEntity.body | +| `sound_enabled` | Boolean | ✅ Yes | Sound preference | NotificationContentEntity.soundEnabled | +| `vibration_enabled` | Boolean | ✅ Yes | Vibration preference | NotificationContentEntity.vibrationEnabled | +| `payload` | String/JSON | ⚠️ Optional | Additional content | ContentCache.payload | +| `created_at` | Long/TimeInterval | ✅ Yes | Creation timestamp | NotificationContentEntity.createdAt | +| `updated_at` | Long/TimeInterval | ✅ Yes | Last update timestamp | NotificationContentEntity.updatedAt | +| `enabled` | Boolean | ✅ Yes | Whether alarm is active | Schedule.enabled | + +### 2.2 Storage Implementation + +**Android**: +* **Primary**: Room database (`DailyNotificationDatabase`) +* **Location**: `android/src/main/java/com/timesafari/dailynotification/` +* **Entities**: `Schedule`, `NotificationContentEntity`, `ContentCache` + +**iOS**: +* **Primary**: UNUserNotificationCenter (OS-managed) +* **Secondary**: Plugin storage (UserDefaults, CoreData, or files) +* **Location**: `ios/Plugin/` +* **Component**: `DailyNotificationStorage?` + +### 2.3 Persistence Validation + +The plugin **MUST**: +* Validate persistence on every alarm schedule +* Log persistence failures +* Handle persistence errors gracefully +* Provide recovery mechanism if persistence fails + +**Acceptance Criteria**: +* **Test**: Schedule alarm, kill app immediately, verify alarm persists in database +* **Test**: Schedule alarm, fill storage, verify graceful error handling +* **Test**: Schedule alarm, corrupt database, verify recovery mechanism +* **Pass**: All persistence operations succeed or fail gracefully with logging + +### 2.4 Data Integrity Rules + +**Required Validation**: +* **MUST** validate all required fields before persistence +* **MUST** validate schedule format (cron or HH:mm) +* **MUST** validate trigger time is in the future (or handle past times appropriately) +* **MUST** ensure foreign key relationships are valid (Schedule → NotificationContentEntity) + +**Data Integrity Constraints**: +* `alarm_id` **MUST** be unique across all schedules +* `scheduleId` **MUST** reference an existing Schedule.id +* `scheduledTime` **MUST** be a valid timestamp +* `deliveryStatus` **MUST** be one of: "pending", "delivered", "missed" + +### 2.5 Failure Modes + +**Persistence Failure Handling**: +* Database errors: Log error, continue with alarm scheduling (best effort), retry persistence +* Storage full: Log warning, attempt cleanup, retry, notify user if critical +* Invalid data: Skip invalid entries, log warning, continue processing +* Migration failures: Rollback to backup, notify user, preserve existing data + +--- + +## 3. Recovery Contract + +### 3.1 Recovery Triggers & Required Actions + +The plugin **MUST** implement recovery at the following points with the specified actions. This is the complete recovery contract that defines what the plugin guarantees for each recovery scenario. + +#### 3.1.1 Boot Event (Android Only) + +**Trigger**: `BOOT_COMPLETED` broadcast + +**Required Actions**: +1. Load all enabled alarms from persistent storage +2. Reschedule each alarm using AlarmManager +3. Detect missed alarms (trigger_time < now) +4. Generate missed alarm events/notifications +5. Log recovery actions + +**Code Reference**: `BootReceiver.kt` line 24 + +**Implementation Status**: See [Phase 3 Implementation](../android-implementation-directive-phase3.md) + +**Platform Reference**: [Android §2.1.2](../01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot) + +**Acceptance Criteria**: +* **Test**: Schedule alarm for 10 minutes, reboot device, do NOT open app, wait 15 minutes +* **Expected**: Alarm does NOT fire (OS limitation - see [Doc A §2.1.2](./01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot)) +* **Test**: Open app after reboot +* **Expected**: Plugin detects missed alarm, reschedules future alarms +* **Pass**: Missed alarm detected exactly once, future alarms rescheduled + +**Missed Alarm Detection Acceptance Criteria**: +* **Test**: Create alarm scheduled for 2 minutes, kill app, wait 5 minutes, launch app +* **Expected**: Plugin detects missed alarm, marks in database (`delivery_status = 'missed'`) +* **Test**: Create repeating alarm, miss 3 occurrences, launch app +* **Expected**: Plugin detects all 3 missed alarms, reschedules next occurrence +* **Pass**: All missed alarms detected and marked, no duplicates, future alarms rescheduled + +--- + +#### 3.1.2 App Cold Start + +**Trigger**: App launched from terminated state + +**Required Actions**: +1. Load all enabled alarms from persistent storage +2. Verify active alarms match stored alarms +3. Detect missed alarms (trigger_time < now) +4. Reschedule future alarms +5. Generate missed alarm events/notifications +6. Log recovery actions + +**Implementation Status**: See [Phase 1 Implementation](../android-implementation-directive-phase1.md) + +**Code Location**: Plugin initialization (`DailyNotificationPlugin.load()` or equivalent) + +**Platform Reference**: [Android §2.1.4](../01-platform-capability-reference.md#214-alarms-can-be-restored-after-app-restart) + +**Acceptance Criteria**: +* **Test**: Schedule alarm for 5 minutes, kill app process (not force stop), wait 10 minutes +* **Expected**: Alarm fires (OS handles) +* **Test**: Launch app after alarm time passed +* **Expected**: Plugin detects missed alarm if alarm did not fire, reschedules future alarms +* **Pass**: Missed alarm detected if applicable, future alarms verified/rescheduled + +--- + +#### 3.1.3 App Warm Start + +**Trigger**: App returning from background + +**Required Actions**: +1. Verify active alarms are still scheduled +2. Detect missed alarms (trigger_time < now) +3. Reschedule if needed +4. Log recovery actions + +**Implementation Status**: Deferred to future phase + +**Platform Reference**: [Android §2.1.4](../01-platform-capability-reference.md#214-alarms-can-be-restored-after-app-restart) + +--- + +#### 3.1.4 Force Stop Recovery (Android Only) + +**Trigger**: App launched after Force Stop + +**Required Actions**: +1. Detect force stop scenario (DB has alarms, AlarmManager has zero) +2. Mark all past alarms as missed +3. Reschedule all future alarms +4. Generate missed alarm events/notifications +5. Log recovery actions + +**Implementation Status**: See [Phase 2 Implementation](../android-implementation-directive-phase2.md) + +**Platform Reference**: [Android §2.2.1](./01-platform-capability-reference.md#221-you-cannot-survive-force-stop), [Android §2.2.2](./01-platform-capability-reference.md#222-you-cannot-auto-resume-after-force-stop) + +**Acceptance Criteria**: +* **Test**: Schedule multiple alarms (past and future), force stop app, wait past scheduled times +* **Expected**: No alarms fire (OS limitation) +* **Test**: Open app after force stop +* **Expected**: Plugin detects force stop scenario, marks all past alarms as missed, reschedules all future alarms +* **Pass**: All past alarms marked as missed, all future alarms rescheduled + +--- + +#### 3.1.5 User Taps Notification + +**Trigger**: User interaction with notification + +**Required Actions**: +1. Launch app (OS handles) +2. Detect if notification was missed +3. Handle notification action +4. Update alarm state if needed + +**Implementation Status**: Basic support exists + +**Platform Reference**: [Android §2.1.1](../01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents), [iOS §3.1.1](../01-platform-capability-reference.md#311-notifications-survive-app-termination) + +--- + +### 3.2 Recovery Matrix + +| Recovery Point | Android | iOS | Implementation Phase | +| -------------- | ------- | --- | -------------------- | +| Boot event | ✅ Required | ❌ Not needed (OS handles) | Phase 3 | +| Cold start | ✅ Required | ✅ Required | Phase 1 | +| Warm start | ⚠️ Optional | ⚠️ Optional | Future | +| Force stop | ✅ Required | N/A | Phase 2 | +| User tap | ✅ Required | ✅ Required | Basic | + +--- + +## 4. Missed Alarm Handling Contract + +### 4.1 Definition + +An alarm is "missed" if: +* `trigger_time < now` +* Alarm was not fired (or firing status unknown) +* Alarm is still enabled +* Alarm has not been manually cancelled + +### 4.2 Detection Triggers + +Missed alarms **MUST** be detected at: +1. **App cold start** - When app launches from terminated state +2. **App warm start** - When app returns from background (optional, future phase) +3. **Boot event** (Android) - When device reboots +4. **Force stop recovery** (Android) - When app launches after force stop + +### 4.3 Required Actions + +When a missed alarm is detected, the plugin **MUST**: + +1. **Detect** missed alarms during recovery +2. **Mark** missed alarms in database (`delivery_status = 'missed'`) +3. **Generate** missed alarm event/notification (optional, future phase) +4. **Reschedule** future occurrences (if repeating) +5. **Log** missed alarm for debugging +6. **Update** alarm state (mark as missed or reschedule) + +### 4.4 Missed Alarm Resolution Rules + +**Resolution Strategy**: + +| Scenario | Detection Time | Resolution Action | Next Occurrence | +| -------- | -------------- | ----------------- | --------------- | +| **Single alarm missed** | Cold start | Mark as missed, do not reschedule | N/A (one-time alarm) | +| **Repeating alarm missed** | Cold start | Mark as missed, reschedule next occurrence | Calculate from current time | +| **Multiple alarms missed** | Force stop recovery | Mark all as missed, reschedule all future | Calculate from current time | +| **Boot recovery** | Boot event | Mark past as missed, reschedule all future | Calculate from current time | + +**State Transition Table**: + +``` +NONE (first launch, empty DB) + ↓ +COLD_START (DB populated, alarms may exist) + ↓ +FORCE_STOP? (detect: DB populated & no scheduled alarms) + ↓ +BOOT (detect: SharedPreferences flag set) + ↓ +Recovery Actions: + - Detect missed alarms + - Mark missed in database + - Reschedule future alarms + - Update state +``` + +**Transition Detection**: +* **NONE → COLD_START**: DB empty on first launch +* **COLD_START → FORCE_STOP**: DB has schedules but AlarmManager has zero alarms +* **COLD_START → BOOT**: SharedPreferences `last_boot_at` flag set +* **Any → Recovery**: Execute recovery actions based on scenario + +### 4.5 Implementation Requirements + +* **MUST** run on app launch (cold/warm start) +* **MUST** run on boot (Android) +* **MUST NOT** duplicate missed alarm notifications +* **MUST** handle timezone changes +* **MUST** handle clock adjustments + +**Implementation Status**: +- Phase 1: Basic detection and marking - See [Phase 1 Implementation](../android-implementation-directive-phase1.md) +- Phase 2: Force stop recovery - See [Phase 2 Implementation](../android-implementation-directive-phase2.md) +- Phase 3: Boot recovery - See [Phase 3 Implementation](../android-implementation-directive-phase3.md) +- Future: Event emission, user notifications + +--- + +## 5. JS/TS API Contract + +### 5.1 JS/TS API Contract + +The plugin **MUST** document and guarantee the following behaviors to JavaScript/TypeScript developers: + +#### 5.1.1 `scheduleDailyNotification(options)` + +**Method Signature**: +```typescript +scheduleDailyNotification(options: { + schedule: string; // Cron or HH:mm format + title: string; + body: string; + sound?: boolean; + vibration?: boolean; + priority?: 'high' | 'normal' | 'low'; + payload?: object; +}): Promise<{ + success: boolean; + alarmId?: string; + error?: { + code: string; + message: string; + action?: 'opened_settings' | 'permission_denied'; + }; +}> +``` + +**Returned Fields**: +* `success` (boolean) - Whether scheduling succeeded +* `alarmId` (string, optional) - Unique identifier for scheduled alarm +* `error` (object, optional) - Error details if scheduling failed + * `code` (string) - Error code for programmatic handling + * `message` (string) - Human-readable error message + * `action` (string, optional) - Action taken (e.g., "opened_settings") + +**Guarantees**: +* ✅ Notification will fire if app is swiped from recents - **MUST** (OS-guaranteed) +* ✅ Notification will fire if app is terminated by OS - **MUST** (OS-guaranteed) +* ⚠️ Notification will fire after reboot **only if**: + * Android: Boot receiver is registered and working - **SHOULD** (best-effort) + * iOS: Automatic (OS handles) - **MUST** (OS-guaranteed) +* ❌ Notification will **NOT** fire after Android Force Stop until app is opened - **MUST NOT** (OS limitation) +* ⚠️ iOS notifications have ±180s timing tolerance - **SHOULD** (OS limitation) + +**Platform Caveats**: +* Android requires `SCHEDULE_EXACT_ALARM` permission on Android 12+ - See [Doc A §5.2](./01-platform-capability-reference.md#52-android-s-exact-alarm-permission-decision-tree) +* Android requires `RECEIVE_BOOT_COMPLETED` permission for reboot recovery - See [Doc A §2.1.2](./01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot) +* iOS requires notification authorization - See [Doc A §3.1.1](./01-platform-capability-reference.md#311-notifications-survive-app-termination) + +**Error States**: +* `EXACT_ALARM_PERMISSION_REQUIRED` - Android 12+ exact alarm permission needed + * **Response**: Plugin opens system settings, returns `action: "opened_settings"` +* `NOTIFICATIONS_DENIED` - Notification permission denied + * **Response**: Plugin shows error message, returns `action: "permission_denied"` +* `SCHEDULE_FAILED` - Scheduling failed (check logs) + * **Response**: Plugin logs error, returns error with message +* `STORAGE_FULL` - Cannot persist alarm (storage full) + * **Response**: Plugin attempts cleanup, retries, or returns error +* `INVALID_SCHEDULE` - Schedule format invalid + * **Response**: Plugin returns error with validation message + +**Platform References**: +* [Android §2.1.1](./01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents) +* [Android §2.1.2](./01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot) +* [Android §2.2.1](./01-platform-capability-reference.md#221-you-cannot-survive-force-stop) +* [iOS §3.1.1](./01-platform-capability-reference.md#311-notifications-survive-app-termination) +* [iOS §3.1.2](./01-platform-capability-reference.md#312-notifications-persist-across-device-reboot) + +**Developer Responsibilities**: +* **MUST** handle permission requests (Android 12+ exact alarm, iOS notification authorization) +* **MUST** handle error responses and provide user feedback +* **SHOULD** check `getNotificationStatus()` to verify alarm scheduling +* **MUST NOT** assume alarms will fire after Force Stop (Android) until app is opened +* **MUST NOT** assume exact timing on iOS (±180s tolerance) +* **SHOULD** implement retry logic for scheduling failures + +**Platform Differences**: +* **Android**: Requires boot receiver for reboot recovery - See [Doc A §2.1.2](./01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot) +* **iOS**: Notifications persist automatically across reboot - See [Doc A §3.1.2](./01-platform-capability-reference.md#312-notifications-persist-across-device-reboot) +* **Android**: App code runs when alarm fires - See [Doc A §2.1.1](./01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents) +* **iOS**: App code does NOT run when notification fires - See [Doc A §3.2.1](./01-platform-capability-reference.md#321-app-code-does-not-run-when-notification-fires) + +--- + +#### 5.1.2 `scheduleDailyReminder(options)` + +**Method Signature**: Same as `scheduleDailyNotification()` but without `payload` option. + +**Guarantees**: +* Same as `scheduleDailyNotification()` above +* Static reminder (no content dependency) +* Fires even if content fetch fails + +--- + +#### 5.1.3 `getNotificationStatus()` + +**Method Signature**: +```typescript +getNotificationStatus(): Promise<{ + pendingCount: number; + lastNotificationTime?: number; + missedAlarms?: Array<{ + alarmId: string; + scheduledTime: number; + detectedAt: number; + }>; + permissions: { + exactAlarm?: boolean; // Android 12+ + notifications?: boolean; // iOS + bootReceiver?: boolean; // Android + }; +}> +``` + +**Returned Fields**: +* `pendingCount` (number) - Number of scheduled alarms +* `lastNotificationTime` (number, optional) - Timestamp of last notification +* `missedAlarms` (array, optional) - Array of missed alarms + * `alarmId` (string) - Unique identifier + * `scheduledTime` (number) - When alarm was scheduled to fire + * `detectedAt` (number) - When missed alarm was detected +* `permissions` (object) - Current permission status + * `exactAlarm` (boolean, optional) - Android 12+ exact alarm permission + * `notifications` (boolean, optional) - iOS notification authorization + * `bootReceiver` (boolean, optional) - Android boot receiver capability + +**Guarantees**: +* Returns current notification status - **MUST** +* Includes pending notifications - **MUST** +* Includes last notification time - **SHOULD** +* May include missed alarm information - **SHOULD** (if detected) + +--- + +### 5.2 API Warnings + +The plugin **MUST** document the following warnings in JS/TS API documentation: + +**Android Warnings**: +* "Notifications will not fire after device reboot unless the app is opened at least once" - See [Doc A §2.1.2](./01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot) +* "Force Stop will prevent all notifications until the app is manually opened" - See [Doc A §2.2.1](./01-platform-capability-reference.md#221-you-cannot-survive-force-stop) +* "Exact alarm permission is required on Android 12+ for precise timing" - See [Doc A §5.2](./01-platform-capability-reference.md#52-android-s-exact-alarm-permission-decision-tree) + +**iOS Warnings**: +* "Notifications have ±180 seconds timing tolerance" - See [Doc A §6.1](./01-platform-capability-reference.md#61-notification-timing-accuracy) +* "App code does not run when notifications fire (unless user interacts)" - See [Doc A §3.2.1](./01-platform-capability-reference.md#321-app-code-does-not-run-when-notification-fires) +* "Background execution is system-controlled and not guaranteed" - See [Doc A §3.1.3](./01-platform-capability-reference.md#313-background-tasks-for-prefetching) + +**Cross-Platform Warnings**: +* "Missed alarms are detected on app launch, not at trigger time" - See [Doc C §4.2](./03-plugin-requirements.md#42-detection-triggers) +* "Repeating alarms must be rescheduled for each occurrence (Android)" - See [Doc A §2.1.2](./01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot) +* "Location-based triggers do not persist across reboot (iOS)" - See [Doc A §3.1.2](./01-platform-capability-reference.md#312-notifications-persist-across-device-reboot) + +--- + +### 5.3 API Error Handling + +The plugin **MUST**: +* Return clear error messages +* Include error codes for programmatic handling +* Open system settings when permission is needed +* Provide actionable guidance in error messages + +**Example Error Response**: +```typescript +{ + code: "EXACT_ALARM_PERMISSION_REQUIRED", + message: "Exact alarm permission required. Please grant 'Alarms & reminders' permission in Settings, then try again.", + action: "opened_settings" // or "permission_denied" +} +``` + +--- + +## 6. Plugin WILL NOT Guarantee + +### 6.1 Hard Boundaries (Cannot Ever Guarantee) + +The plugin **MUST NOT** promise or attempt to guarantee: + +| Behavior | Platform | Reason | Platform Reference | +| -------- | -------- | ------ | ------------------ | +| **Auto-recovery after Force Stop** | Android | OS hard kill - cannot bypass | [Doc A §2.2.1](./01-platform-capability-reference.md#221-you-cannot-survive-force-stop) | +| **App code execution on iOS notification fire** | iOS | OS limitation - code does not run | [Doc A §3.2.1](./01-platform-capability-reference.md#321-app-code-does-not-run-when-notification-fires) | +| **Exact timing on iOS** | iOS | ±180s tolerance is OS limitation | [Doc A §6.1](./01-platform-capability-reference.md#61-notification-timing-accuracy) | +| **Background execution timing** | iOS | System-controlled, not guaranteed | [Doc A §3.1.3](./01-platform-capability-reference.md#313-background-tasks-for-prefetching) | +| **Alarm persistence without storage** | Both | Must persist in durable storage | [Doc A §2.2.3](./01-platform-capability-reference.md#223-alarms-cannot-be-preserved-solely-in-ram) | + +**Enforcement**: These behaviors **MUST** be documented as unsupported in JS/TS API documentation. + +--- + +### 6.2 Guarantee vs Best-Effort Matrix + +| Behavior | Android | iOS | Guarantee Level | Notes | +| -------- | ------- | --- | -------------- | ----- | +| **Notification fires after swipe** | ✅ Guaranteed | ✅ Guaranteed | **MUST** - OS-guaranteed | [Doc A §2.1.1](./01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents), [Doc A §3.1.1](./01-platform-capability-reference.md#311-notifications-survive-app-termination) | +| **Notification fires after reboot** | ⚠️ Best-effort | ✅ Guaranteed | Android: **SHOULD** (requires boot receiver), iOS: **MUST** | [Doc A §2.1.2](./01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot), [Doc A §3.1.2](./01-platform-capability-reference.md#312-notifications-persist-across-device-reboot) | +| **Missed alarm detection** | ✅ Guaranteed | ✅ Guaranteed | **MUST** - Plugin-required | [Doc C §4.2](./03-plugin-requirements.md#42-detection-triggers) | +| **Force stop recovery** | ✅ Guaranteed | N/A | **MUST** - Plugin-required | [Doc C §3.1.4](./03-plugin-requirements.md#314-force-stop-recovery-android-only) | +| **Exact timing** | ⚠️ Best-effort | ⚠️ Best-effort | Android: **SHOULD** (with permission), iOS: **SHOULD** (±180s) | [Doc A §5.2](./01-platform-capability-reference.md#52-android-s-exact-alarm-permission-decision-tree), [Doc A §6.1](./01-platform-capability-reference.md#61-notification-timing-accuracy) | +| **Background execution** | ⚠️ Best-effort | ⚠️ Best-effort | **SHOULD** - System-controlled | [Doc A §3.1.3](./01-platform-capability-reference.md#313-background-tasks-for-prefetching) | + +**Legend**: +- **MUST** = Plugin guarantees this behavior +- **SHOULD** = Plugin attempts this but cannot guarantee (best-effort) +- **MUST NOT** = Plugin cannot and will not attempt this + +--- + +### 6.3 Failure Modes & Required Responses + +| Failure Mode | Platform | Required Plugin Response | User Impact | +| ------------ | -------- | ------------------------ | ----------- | +| **Persistence failure** | Both | Log error, continue with alarm scheduling (best effort), retry persistence | Alarm may be lost if app killed | +| **Storage full** | Both | Log warning, attempt cleanup, retry, notify user if critical | User must free space | +| **Invalid data** | Both | Skip invalid entries, log warning, continue processing | Invalid alarms ignored | +| **Permission denied** | Android | Open settings, show error message, return error code | User must grant permission | +| **Boot receiver not registered** | Android | Log error, alarms not rescheduled on boot | Alarms lost on reboot | +| **Force stop** | Android | Detect on app restart, recover all alarms, mark missed | Alarms lost until app opened | +| **Background execution denied** | iOS | Log warning, schedule next attempt, notify user | Prefetch may fail | +| **Notification authorization denied** | iOS | Show error, return error code, cannot schedule | Notifications disabled | + +**Required Actions**: +* **MUST** log all failures with sufficient context +* **MUST** provide user-visible error messages for critical failures +* **SHOULD** provide recovery mechanisms where possible +* **MUST NOT** crash or silently fail + +--- + +## 7. Explicit Storage Schema + +### 7.1 Required Database Schema + +**Android (Room Database)**: + +| Table | Field | Type | Required | Purpose | +| ----- | ----- | ----- | -------- | ------- | +| **Schedule** | `id` | String | ✅ Yes | Unique identifier | +| | `kind` | String | ✅ Yes | "notify" or "fetch" | +| | `enabled` | Boolean | ✅ Yes | Whether alarm is active | +| | `cron` | String? | ⚠️ Optional | Cron expression | +| | `clockTime` | String? | ⚠️ Optional | HH:mm format | +| | `nextRunAt` | Long? | ⚠️ Optional | Next scheduled time | +| | `createdAt` | Long | ✅ Yes | Creation timestamp | +| | `updatedAt` | Long | ✅ Yes | Last update timestamp | +| **NotificationContentEntity** | `id` | String | ✅ Yes | Unique identifier | +| | `scheduleId` | String | ✅ Yes | Foreign key to Schedule | +| | `title` | String | ✅ Yes | Notification title | +| | `body` | String | ✅ Yes | Notification body | +| | `scheduledTime` | Long | ✅ Yes | When to fire | +| | `deliveryStatus` | String | ✅ Yes | "pending", "delivered", "missed" | +| | `priority` | String | ✅ Yes | Notification priority | +| | `soundEnabled` | Boolean | ✅ Yes | Sound preference | +| | `vibrationEnabled` | Boolean | ✅ Yes | Vibration preference | +| | `createdAt` | Long | ✅ Yes | Creation timestamp | +| | `updatedAt` | Long | ✅ Yes | Last update timestamp | +| **ContentCache** | `id` | String | ✅ Yes | Unique identifier | +| | `scheduleId` | String | ✅ Yes | Foreign key to Schedule | +| | `payload` | String? | ⚠️ Optional | Additional content (JSON) | +| | `createdAt` | Long | ✅ Yes | Creation timestamp | + +**iOS (Plugin Storage)**: + +| Storage Type | Field | Type | Required | Purpose | +| ------------ | ----- | ----- | -------- | ------- | +| **UserDefaults/CoreData** | `alarm_id` | String | ✅ Yes | Unique identifier | +| | `trigger_time` | TimeInterval | ✅ Yes | When to fire | +| | `repeat_rule` | String | ✅ Yes | NONE, DAILY, WEEKLY, CUSTOM | +| | `title` | String | ✅ Yes | Notification title | +| | `body` | String | ✅ Yes | Notification body | +| | `enabled` | Boolean | ✅ Yes | Whether alarm is active | +| | `created_at` | TimeInterval | ✅ Yes | Creation timestamp | +| | `updated_at` | TimeInterval | ✅ Yes | Last update timestamp | + +**Note**: iOS also uses UNUserNotificationCenter (OS-managed) for notification scheduling, but plugin **MUST** maintain parallel storage for missed detection. + +--- + +## 8. Explicit Unsupported Behaviors + +### 8.1 Hard Boundaries (Plugin WILL NOT Guarantee) + +The plugin **MUST NOT** support or guarantee the following behaviors: + +| Behavior | Platform | Reason | Platform Reference | +| -------- | -------- | ------ | ------------------ | +| **Auto-recovery after Android Force Stop** | Android | OS hard kill - cannot bypass | [Doc A §2.2.1](./01-platform-capability-reference.md#221-you-cannot-survive-force-stop) | +| **App code execution on iOS notification fire** | iOS | OS limitation - code does not run | [Doc A §3.2.1](./01-platform-capability-reference.md#321-app-code-does-not-run-when-notification-fires) | +| **Exact timing on iOS** | iOS | ±180s tolerance is OS limitation | [Doc A §6.1](./01-platform-capability-reference.md#61-notification-timing-accuracy) | +| **Background execution timing guarantees** | Both | System-controlled, not guaranteed | [Doc A §3.1.3](./01-platform-capability-reference.md#313-background-tasks-for-prefetching) | +| **Alarm persistence without durable storage** | Both | Must use database/files | [Doc A §2.2.3](./01-platform-capability-reference.md#223-alarms-cannot-be-preserved-solely-in-ram) | +| **Repeating alarms without rescheduling** | Android | Must reschedule each occurrence | [Doc A §2.1.2](./01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot) | +| **Location-based triggers** | iOS | Does not persist across reboot | [Doc A §3.1.2](./01-platform-capability-reference.md#312-notifications-persist-across-device-reboot) | +| **Silent rescheduling without user launch** | Android | Force stop blocks all execution | [Doc A §2.2.1](./01-platform-capability-reference.md#221-you-cannot-survive-force-stop) | + +**Documentation Requirement**: All unsupported behaviors **MUST** be documented in JS/TS API with clear warnings. See [§5.2 - API Warnings](./03-plugin-requirements.md#52-api-warnings). + +--- + +### 8.2 Platform-Specific Limitations + +**Android**: +* Force Stop is a hard kill that cannot be bypassed - See [Doc A §2.2.1](./01-platform-capability-reference.md#221-you-cannot-survive-force-stop) +* Reboot wipes all alarms; must reschedule from storage - See [Doc A §2.1.2](./01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot) +* Exact alarm permission required on Android 12+ - See [Doc A §5.2](./01-platform-capability-reference.md#52-android-s-exact-alarm-permission-decision-tree) +* Doze mode may defer inexact alarms - See [Doc A §2.1.1](./01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents) + +**iOS**: +* Background execution is severely limited - See [Doc A §3.1.3](./01-platform-capability-reference.md#313-background-tasks-for-prefetching) +* App code does not run when notifications fire - See [Doc A §3.2.1](./01-platform-capability-reference.md#321-app-code-does-not-run-when-notification-fires) +* Timing has ±180s tolerance - See [Doc A §6.1](./01-platform-capability-reference.md#61-notification-timing-accuracy) +* BGTaskScheduler execution is not guaranteed - See [Doc A §3.1.3](./01-platform-capability-reference.md#313-background-tasks-for-prefetching) + +--- + +## 9. Traceability Matrix + +**Complete mapping from platform facts → requirements → tests → implementation**: + +| Requirement | Platform Source (Doc A) | Expected Plugin Behavior (Doc C) | Test Scenario (Doc B) | Implemented in Phase | +| ----------- | ----------------------- | ------------------------------- | --------------------- | -------------------- | +| **Missed alarm detection** | [Android §2.1.4](./01-platform-capability-reference.md#214-alarms-can-be-restored-after-app-restart) | [§4.2 - Detection Triggers](./03-plugin-requirements.md#42-detection-triggers) | [Test 4 Step 6](./02-plugin-behavior-exploration.md#test-4-device-reboot) | Phase 1 | +| **Boot reschedule** | [Android §2.1.2](./01-platform-capability-reference.md#212-alarms-can-be-preserved-across-device-reboot) | [§3.1.1 - Boot Event](./03-plugin-requirements.md#311-boot-event-android-only) | [Test 4 Step 7](./02-plugin-behavior-exploration.md#test-4-device-reboot) | Phase 3 | +| **Force stop recovery** | [Android §2.2.1](./01-platform-capability-reference.md#221-you-cannot-survive-force-stop) | [§3.1.4 - Force Stop Recovery](./03-plugin-requirements.md#314-force-stop-recovery-android-only) | [Test 5](./02-plugin-behavior-exploration.md#test-5-android-force-stop) | Phase 2 | +| **Cold start recovery** | [Android §2.1.4](./01-platform-capability-reference.md#214-alarms-can-be-restored-after-app-restart) | [§3.1.2 - App Cold Start](./03-plugin-requirements.md#312-app-cold-start) | [Test 4 Step 5](./02-plugin-behavior-exploration.md#test-4-device-reboot) | Phase 1 | +| **Swipe from recents** | [Android §2.1.1](./01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents), [iOS §3.1.1](./01-platform-capability-reference.md#311-notifications-survive-app-termination) | [§1.1 - Guarantees Matrix](./03-plugin-requirements.md#11-cross-platform-guarantees-matrix) | [Test 2](./02-plugin-behavior-exploration.md#test-2-swipe-from-recents) | OS-guaranteed | +| **iOS notification persistence** | [iOS §3.1.2](./01-platform-capability-reference.md#312-notifications-persist-across-device-reboot) | [§1.1 - Guarantees Matrix](./03-plugin-requirements.md#11-cross-platform-guarantees-matrix) | [Test 3](./02-plugin-behavior-exploration.md#test-3-device-reboot-1) | OS-guaranteed | +| **Persistence** | [Android §2.2.3](./01-platform-capability-reference.md#223-alarms-cannot-be-preserved-solely-in-ram) | [§2 - Persistence Requirements](./03-plugin-requirements.md#2-persistence-requirements) | [Persistence Investigation](./02-plugin-behavior-exploration.md#13-persistence-investigation) | Phase 1 | +| **Exact alarm permission** | [Android §5.2](./01-platform-capability-reference.md#52-android-s-exact-alarm-permission-decision-tree) | [§10.1.1 - Permissions](./03-plugin-requirements.md#1011-permissions) | [Test 6](./02-plugin-behavior-exploration.md#test-6-exact-alarm-permission-android-12) | Phase 1 | + +--- + +## 10. Platform-Specific Requirements + +### 10.1 Android Requirements + +#### 10.1.1 Permissions + +**Required Permissions**: +* `RECEIVE_BOOT_COMPLETED` - Boot receiver +* `SCHEDULE_EXACT_ALARM` - Android 12+ (API 31+) for exact alarms +* `POST_NOTIFICATIONS` - Android 13+ (API 33+) for notifications + +**Permission Handling**: +* Check permission before scheduling +* Request permission if not granted +* Open system settings if permission denied +* Provide clear error messages + +**Code Reference**: `DailyNotificationPlugin.kt` line 1309 + +**Platform Reference**: [Android §2.2.4](../01-platform-capability-reference.md#224-you-cannot-bypass-doze-or-battery-optimization-restrictions-without-permission) + +--- + +#### 10.1.2 Manifest Entries + +**Required Manifest Entries**: +```xml + + + + + + + + + + + + + + + + +``` + +**Location**: Test app manifest: `test-apps/android-test-app/app/src/main/AndroidManifest.xml` + +--- + +#### 10.1.3 Notification Channels + +**Required Channels**: +* `timesafari.daily` - Primary notification channel +* `daily_reminders` - Reminder notifications (if used) + +**Channel Configuration**: +* Importance: HIGH (for alarms), DEFAULT (for reminders) +* Sound: Enabled by default +* Vibration: Enabled by default +* Show badge: Enabled + +**Code Reference**: `ChannelManager.java` or `NotifyReceiver.kt` line 454 + +--- + +#### 10.1.4 Alarm Scheduling + +**Required API Usage**: +* `setAlarmClock()` for Android 5.0+ (preferred) +* `setExactAndAllowWhileIdle()` for Android 6.0+ (fallback) +* `setExact()` for older versions (fallback) + +**Code Reference**: `NotifyReceiver.kt` line 219, 223, 231 + +**Platform Reference**: [Android §2.1.1](../01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents) + +--- + +### 10.2 iOS Requirements + +#### 10.2.1 Permissions + +**Required Permissions**: +* Notification authorization (requested at runtime) + +**Permission Handling**: +* Request permission before scheduling +* Handle authorization status +* Provide clear error messages + +--- + +#### 10.2.2 Background Tasks + +**Required Background Task Identifiers**: +* `com.timesafari.dailynotification.fetch` - Background fetch +* `com.timesafari.dailynotification.notify` - Notification task (if used) + +**Background Task Registration**: +* Register in `Info.plist`: + ```xml + BGTaskSchedulerPermittedIdentifiers + + com.timesafari.dailynotification.fetch + + ``` + +**Code Reference**: `DailyNotificationPlugin.swift` line 31-32 + +**Platform Reference**: [iOS §3.1.3](../01-platform-capability-reference.md#313-background-tasks-for-prefetching) + +--- + +#### 10.2.3 Notification Scheduling + +**Required API Usage**: +* `UNUserNotificationCenter.add()` - Schedule notifications +* `UNCalendarNotificationTrigger` - Calendar-based triggers (preferred) +* `UNTimeIntervalNotificationTrigger` - Time interval triggers + +**Code Reference**: `DailyNotificationScheduler.swift` line 185 + +**Platform Reference**: [iOS §3.1.1](../01-platform-capability-reference.md#311-notifications-survive-app-termination) + +--- + +#### 10.2.4 Notification Categories + +**Required Categories**: +* `DAILY_NOTIFICATION` - Primary notification category + +**Category Configuration**: +* Actions: Configure as needed +* Options: Custom sound, custom actions + +**Code Reference**: `DailyNotificationScheduler.swift` line 62+ + +--- + +## 11. Testing Requirements + +### 11.1 Required Test Scenarios + +The plugin **must** be tested for: + +**Android**: +* [ ] Base case (alarm fires on time) +* [ ] Swipe from recents +* [ ] OS kill (memory pressure) +* [ ] Device reboot (with and without app launch) +* [ ] Force stop (with app restart) +* [ ] Exact alarm permission (Android 12+) +* [ ] Boot receiver functionality +* [ ] Missed alarm detection + +**iOS**: +* [ ] Base case (notification fires on time) +* [ ] Swipe app away +* [ ] Device reboot (without app launch) +* [ ] Hard termination and relaunch +* [ ] Background execution limits +* [ ] Missed notification detection + +**Cross-Platform**: +* [ ] Timezone changes +* [ ] Clock adjustments +* [ ] Multiple simultaneous alarms +* [ ] Repeating alarms +* [ ] Alarm cancellation + +**Test Documentation**: See [Plugin Behavior Exploration](./02-plugin-behavior-exploration.md) for test matrices. + +--- + +## 12. Migration Rules + +### 12.1 Storage Format Changes + +**If plugin storage format changes**, migration **MUST** define: + +1. **Version detection**: How to identify old vs new format +2. **Safe upgrade path**: Step-by-step migration process +3. **Failure fallback**: What happens if migration fails +4. **No data loss**: All existing alarms must be preserved or explicitly marked as lost + +**Migration Requirements**: +* **MUST** run automatically on app launch +* **MUST** log migration steps for debugging +* **MUST** handle partial migrations gracefully +* **MUST NOT** lose user data without explicit user confirmation + +**Example Migration**: +```kotlin +// Pseudo-code +if (databaseVersion < currentVersion) { + migrateDatabase(oldVersion, currentVersion) + verifyMigrationIntegrity() + if (migrationFailed) { + rollbackToBackup() + notifyUser() + } +} +``` + +### 12.2 API Contract Changes + +**If JS/TS API changes**: +* **MUST** maintain backward compatibility for at least one major version +* **MUST** provide deprecation warnings before removal +* **MUST** document migration path in changelog +* **MUST** update Doc C before releasing breaking changes + +--- + +## 13. API Stability Guarantees + +### 13.1 Breaking Change Policy + +**No breaking change may occur without**: + +1. **Doc C update** - Requirements document updated first +2. **MINOR or MAJOR version bump** - Semantic versioning enforced +3. **Migration notes** - Clear upgrade path documented +4. **Deprecation period** - Old API deprecated for at least one release cycle + +### 13.2 Stability Guarantees + +**Plugin guarantees**: +* **MUST** maintain API contract within same major version +* **SHOULD** maintain API contract across minor versions (exceptions documented) +* **MUST NOT** break API contract without major version bump + +**Example**: +* `v1.0.0` → `v1.1.0`: New features added, no breaking changes ✅ +* `v1.1.0` → `v2.0.0`: Breaking API change, migration guide required ✅ +* `v1.1.0` → `v1.2.0`: Breaking API change ❌ (must be v2.0.0) + +--- + +## 14. "What Happens If We Do Nothing?" Analysis + +### 14.1 No Recovery Implementation + +**If plugin does NOT implement recovery**: + +| Scenario | What Happens | Impact | +| -------- | ------------ | ------ | +| **Device Reboot** (Android) | All alarms wiped, never fire | **Critical**: User loses all scheduled alarms | +| **Force Stop** (Android) | All alarms cleared, never fire until app opened | **Critical**: User loses alarms until manual app launch | +| **Cold Start** | Missed alarms never detected | **Major**: User unaware of missed notifications | +| **Warm Start** | Alarms may be missing, never verified | **Minor**: Edge case, alarms usually still work | + +**Conclusion**: Recovery **MUST** be implemented for Android reboot and force stop scenarios. Cold start recovery **SHOULD** be implemented for user experience. + +### 14.2 No Persistence + +**If plugin does NOT persist alarms**: + +| Scenario | What Happens | Impact | +| -------- | ------------ | ------ | +| **App Kill** | All alarms lost | **Critical**: User loses all scheduled alarms | +| **Reboot** | Cannot reschedule | **Critical**: Recovery impossible | +| **Force Stop** | Cannot detect missed alarms | **Major**: Recovery incomplete | + +**Conclusion**: Persistence **MUST** be implemented. It is foundational for all recovery scenarios. + +### 14.3 No Missed Alarm Detection + +**If plugin does NOT detect missed alarms**: + +| Scenario | What Happens | Impact | +| -------- | ------------ | ------ | +| **Missed Alarm** | User never notified | **Major**: User unaware of missed notifications | +| **Repeating Alarm** | Next occurrence may be wrong | **Major**: Schedule drift over time | + +**Conclusion**: Missed alarm detection **SHOULD** be implemented for user experience and schedule accuracy. + +--- + +## 15. Versioning Requirements + +### 15.1 Breaking Changes + +Any change to alarm behavior is **breaking** and requires: + +* **MAJOR version bump** (semantic versioning) +* **Migration guide** for existing users +* **Deprecation warnings** (if applicable) +* **Clear changelog entry** + +### 10.2 Non-Breaking Changes + +Non-breaking changes include: +* Bug fixes +* Performance improvements +* Additional features (backward compatible) +* Documentation updates + +--- + +## 16. Cross-Reference Anchors + +**All cross-references use standardized anchors**: + +| Reference Format | Example | Resolves To | +| ---------------- | ------- | ----------- | +| `[Doc A §X.Y]` | `[Doc A §2.1.1]` | [Platform Reference §2.1.1](./01-platform-capability-reference.md#211-alarms-survive-ui-kills-swipe-from-recents) | +| `[Doc B §X.Y]` | `[Doc B §1.2]` | [Exploration §1.2](./02-plugin-behavior-exploration.md#12-behavior-testing-matrix) | +| `[Doc C §X.Y]` | `[Doc C §3.1.2]` | [Requirements §3.1.2](#312-app-cold-start) | +| `[Phase N §X.Y]` | `[Phase 1 §2.3]` | [Phase 1 Implementation](../android-implementation-directive-phase1.md#23-cold-start-recovery) | + +**Anchor Rules**: +* All section headers MUST have unique anchors +* Cross-references MUST use exact anchor format +* Broken links MUST be fixed immediately + +--- + +## Related Documentation + +- [Platform Capability Reference](./01-platform-capability-reference.md) - OS-level facts +- [Plugin Behavior Exploration](./02-plugin-behavior-exploration.md) - Exploration template +- [Unified Alarm Directive](./000-UNIFIED-ALARM-DIRECTIVE.md) - Master coordination document +- [Phase 1: Cold Start Recovery](../android-implementation-directive-phase1.md) - Implementation +- [Phase 2: Force Stop Recovery](../android-implementation-directive-phase2.md) - Implementation +- [Phase 3: Boot Recovery](../android-implementation-directive-phase3.md) - Implementation + +--- + +## Version History + +- **v1.1.0** (November 2025): Enhanced with non-guarantees, failure modes, storage schema + - Added "Plugin WILL NOT Guarantee" section + - Added Guarantee vs Best-Effort matrix + - Added Failure Modes & Required Responses table + - Added Explicit Storage Schema + - Enhanced JS/TS API contract with returned fields and error states + - Added Missed Alarm Resolution Rules + - Added State Transition Table + - Added Explicit Unsupported Features List + +- **v1.0.0** (November 2025): Initial requirements document + - Plugin behavior guarantees and limitations + - Persistence requirements + - Recovery requirements + - Missed alarm handling contract + - JS/TS API contract + - Platform-specific requirements + - Testing requirements + - Traceability matrix + diff --git a/docs/android-alarm-persistence-directive.md b/docs/android-alarm-persistence-directive.md index 4c3ad5d..418f005 100644 --- a/docs/android-alarm-persistence-directive.md +++ b/docs/android-alarm-persistence-directive.md @@ -1,8 +1,12 @@ # Android Alarm Persistence, Recovery, and Limitations +**⚠️ DEPRECATED**: This document has been superseded by [01-platform-capability-reference.md](./alarms/01-platform-capability-reference.md) as part of the unified alarm documentation structure. + +**See**: [Unified Alarm Directive](./alarms/000-UNIFIED-ALARM-DIRECTIVE.md) for the new documentation structure. + **Author**: Matthew Raymer **Date**: November 2025 -**Status**: Engineering Directive - Active Reference +**Status**: **DEPRECATED** - Superseded by unified structure ## Purpose diff --git a/docs/android-implementation-directive-phase1.md b/docs/android-implementation-directive-phase1.md index db8bd5a..a0af6f9 100644 --- a/docs/android-implementation-directive-phase1.md +++ b/docs/android-implementation-directive-phase1.md @@ -3,7 +3,10 @@ **Author**: Matthew Raymer **Date**: November 2025 **Status**: Phase 1 - Minimal Viable Recovery -**Version**: 1.0.0 +**Version**: 1.0.0 +**Last Synced With Plugin Version**: v1.1.0 + +**Implements**: [Plugin Requirements §3.1.2 - App Cold Start](./alarms/03-plugin-requirements.md#312-app-cold-start) ## Purpose @@ -12,6 +15,8 @@ Phase 1 implements **minimal viable app launch recovery** for cold start scenari **Scope**: Phase 1 implements **cold start recovery only**. Force stop detection, warm start optimization, and boot receiver enhancements are **out of scope** for this phase and deferred to later phases. **Reference**: +- [Plugin Requirements](./alarms/03-plugin-requirements.md) - Requirements this phase implements +- [Platform Capability Reference](./alarms/01-platform-capability-reference.md) - OS-level facts - [Full Implementation Directive](./android-implementation-directive.md) - Complete scope - [Phase 2: Force Stop Recovery](./android-implementation-directive-phase2.md) - Next phase - [Phase 3: Boot Receiver Enhancement](./android-implementation-directive-phase3.md) - Final phase diff --git a/docs/android-implementation-directive-phase2.md b/docs/android-implementation-directive-phase2.md index 8b3afe5..6bf6770 100644 --- a/docs/android-implementation-directive-phase2.md +++ b/docs/android-implementation-directive-phase2.md @@ -3,7 +3,10 @@ **Author**: Matthew Raymer **Date**: November 2025 **Status**: Phase 2 - Force Stop Recovery -**Version**: 1.0.0 +**Version**: 1.0.0 +**Last Synced With Plugin Version**: v1.1.0 + +**Implements**: [Plugin Requirements §3.1.4 - Force Stop Recovery](./alarms/03-plugin-requirements.md#314-force-stop-recovery-android-only) ## Purpose @@ -15,7 +18,11 @@ Phase 2 implements **force stop detection and comprehensive recovery**. This han **Scope**: Force stop detection, scenario differentiation, and full alarm recovery. -**Reference**: See [Phase 1](./android-implementation-directive-phase1.md) for cold start recovery, [Full Implementation Directive](./android-implementation-directive.md) for complete scope. +**Reference**: +- [Plugin Requirements](./alarms/03-plugin-requirements.md) - Requirements this phase implements +- [Platform Capability Reference](./alarms/01-platform-capability-reference.md) - OS-level facts +- [Phase 1](./android-implementation-directive-phase1.md) - Prerequisite +- [Full Implementation Directive](./android-implementation-directive.md) - Complete scope --- diff --git a/docs/android-implementation-directive-phase3.md b/docs/android-implementation-directive-phase3.md index 7acbed9..9f6159f 100644 --- a/docs/android-implementation-directive-phase3.md +++ b/docs/android-implementation-directive-phase3.md @@ -3,7 +3,10 @@ **Author**: Matthew Raymer **Date**: November 2025 **Status**: Phase 3 - Boot Recovery Enhancement -**Version**: 1.0.0 +**Version**: 1.0.0 +**Last Synced With Plugin Version**: v1.1.0 + +**Implements**: [Plugin Requirements §3.1.1 - Boot Event](./alarms/03-plugin-requirements.md#311-boot-event-android-only) ## Purpose @@ -15,7 +18,12 @@ Phase 3 enhances the **boot receiver** to detect and handle missed alarms during **Dependencies**: Boot receiver behavior assumes that Phase 1 and Phase 2 definitions of 'missed alarm', 'next occurrence', and `Schedule`/`NotificationContentEntity` semantics are already in place. -**Reference**: See [Phase 1](./android-implementation-directive-phase1.md) and [Phase 2](./android-implementation-directive-phase2.md) for app launch recovery, [Full Implementation Directive](./android-implementation-directive.md) for complete scope. +**Reference**: +- [Plugin Requirements](./alarms/03-plugin-requirements.md) - Requirements this phase implements +- [Platform Capability Reference](./alarms/01-platform-capability-reference.md) - OS-level facts +- [Phase 1](./android-implementation-directive-phase1.md) - Prerequisite +- [Phase 2](./android-implementation-directive-phase2.md) - Prerequisite +- [Full Implementation Directive](./android-implementation-directive.md) - Complete scope **Boot vs App Launch Recovery**: diff --git a/docs/android-implementation-directive.md b/docs/android-implementation-directive.md index 35aa8a0..5be4cf4 100644 --- a/docs/android-implementation-directive.md +++ b/docs/android-implementation-directive.md @@ -15,20 +15,27 @@ This directive provides **descriptive overview and integration guidance** for An **⚠️ CRITICAL**: This document is **descriptive and integrative**. The **normative implementation instructions** are in the Phase 1–3 directives below. **If any code or behavior in this file conflicts with a Phase directive, the Phase directive wins.** +**Reference**: See [Plugin Requirements](./alarms/03-plugin-requirements.md) for requirements that Phase directives implement. + **Reference**: See [Exploration Findings](./exploration-findings-initial.md) for gap analysis. **⚠️ IMPORTANT**: For implementation, use the phase-specific directives (these are the canonical source of truth): - **[Phase 1: Cold Start Recovery](./android-implementation-directive-phase1.md)** - Minimal viable recovery + - Implements: [Plugin Requirements §3.1.2](./alarms/03-plugin-requirements.md#312-app-cold-start) - Explicit acceptance criteria, rollback safety, data integrity checks - **Start here** for fastest implementation - **[Phase 2: Force Stop Detection & Recovery](./android-implementation-directive-phase2.md)** - Comprehensive force stop handling + - Implements: [Plugin Requirements §3.1.4](./alarms/03-plugin-requirements.md#314-force-stop-recovery-android-only) - Prerequisite: Phase 1 complete - **[Phase 3: Boot Receiver Missed Alarm Handling](./android-implementation-directive-phase3.md)** - Boot recovery enhancement + - Implements: [Plugin Requirements §3.1.1](./alarms/03-plugin-requirements.md#311-boot-event-android-only) - Prerequisites: Phase 1 and Phase 2 complete +**See Also**: [Unified Alarm Directive](./alarms/000-UNIFIED-ALARM-DIRECTIVE.md) for master coordination document. + --- ## 1. Implementation Overview diff --git a/docs/platform-capability-reference.md b/docs/platform-capability-reference.md index ed5bafd..d413718 100644 --- a/docs/platform-capability-reference.md +++ b/docs/platform-capability-reference.md @@ -1,8 +1,12 @@ # Platform Capability Reference: Android & iOS Alarm/Notification Behavior +**⚠️ DEPRECATED**: This document has been superseded by [01-platform-capability-reference.md](./alarms/01-platform-capability-reference.md) as part of the unified alarm documentation structure. + +**See**: [Unified Alarm Directive](./alarms/000-UNIFIED-ALARM-DIRECTIVE.md) for the new documentation structure. + **Author**: Matthew Raymer **Date**: November 2025 -**Status**: Platform Reference - Stable +**Status**: **DEPRECATED** - Superseded by unified structure ## Purpose