Files
daily-notification-plugin/docs/platform/ios/RECOVERY_SCENARIO_MAPPING.md
Matthew Raymer c39bd7cec6 docs: Consolidate documentation structure (139 files, zero information loss)
Consolidate all markdown documentation into organized structure per
CONSOLIDATION_DIRECTIVE. All files preserved (canonical, merged, or archived).

- docs/integration/ - Integration documentation (7 files)
- docs/platform/ios/ - iOS platform docs (12 files)
- docs/platform/android/ - Android platform docs (9 files)
- docs/testing/ - Testing documentation (15 files)
- docs/design/ - Design & research (5 files)
- docs/ai/ - AI/ChatGPT artifacts (7 files)
- docs/archive/2025-legacy-doc/ - Historical docs (17 files)

- Integration: Root INTEGRATION_GUIDE.md → docs/integration/
- Platform: Separated iOS and Android into platform/ subdirectories
- Testing: Consolidated all testing docs to docs/testing/
- Legacy: Archived entire doc/ directory to archive/
- AI: Moved all ChatGPT artifacts to docs/ai/

- Added docs/00-INDEX.md - Central navigation hub
- Added docs/CONSOLIDATION_SOURCE_MAP.md - Complete audit trail
- Added docs/CONSOLIDATION_COMPLETE.md - Consolidation summary
- Updated README.md with links to documentation index

- All 139 files have destinations (see CONSOLIDATION_SOURCE_MAP.md)
- Zero information loss (all files preserved)
- Archive preserves original structure
- Index provides clear navigation

- 87 files moved/created/updated
- Root-level docs consolidated
- Legacy doc/ directory archived
- Test app docs remain with test apps (indexed)

Ref: CONSOLIDATION_DIRECTIVE
Author: Matthew Raymer
2025-12-18 09:13:18 +00:00

11 KiB

iOS Recovery Scenario Mapping: Android → iOS Equivalents

Author: Matthew Raymer
Date: 2025-12-08
Status: 🎯 ACTIVE - Recovery Scenario Mapping Reference
Version: 1.0.0
Last Synced With Plugin Version: v1.1.0

Purpose

This document maps Android recovery scenarios to their iOS equivalents, providing a clear translation guide for implementing iOS recovery logic based on Android patterns.

Reference:


1. Scenario Mapping Overview

1.1 Direct Mappings

Android Scenario iOS Equivalent Detection Method Recovery Action
COLD_START App Launch After Termination Compare UNUserNotificationCenter vs DB Detect missed, verify future
FORCE_STOP App Terminated by System DB has schedules, no notifications Full recovery of all schedules
BOOT Device Reboot BGTaskScheduler registration Reschedule all notifications
WARM_START App Resume (Foreground) Notifications match DB state No recovery needed (optimization)
NONE First Launch / No Recovery Empty database No action needed

1.2 Key Differences

iOS Advantages:

  • Notifications persist across termination (OS-guaranteed)
  • Notifications persist across reboot (OS-guaranteed)
  • No user-facing "force stop" equivalent

iOS Challenges:

  • App code does NOT run when notification fires
  • Must detect missed notifications on app launch
  • Background execution severely limited

2. Detailed Scenario Mappings

2.1 COLD_START → App Launch After Termination

Android Definition:

  • Process killed, alarms may or may not exist
  • Database still populated
  • Alarms may have been cleared by OS

iOS Equivalent:

  • App terminated by system or user
  • Notifications may still exist (OS-guaranteed persistence)
  • Database still populated
  • Need to verify notification state matches database

Detection Logic:

Android:

// Check if alarms exist in AlarmManager
val alarmsExist = alarmManager.hasAlarm(pendingIntent)
if (alarmsExist && dbHasSchedules) {
    return COLD_START
}

iOS:

// Check if notifications exist in UNUserNotificationCenter
let pendingNotifications = try await notificationCenter.pendingNotificationRequests()
let dbSchedules = try database.getEnabledSchedules()

if !pendingNotifications.isEmpty && !dbSchedules.isEmpty {
    // Compare notification IDs with DB state
    let dbIds = Set(dbSchedules.flatMap { $0.getScheduledNotificationIds() })
    let pendingIds = Set(pendingNotifications.map { $0.identifier })
    
    if dbIds != pendingIds {
        return .coldStart  // Mismatch indicates recovery needed
    }
}

Recovery Actions:

  1. Detect missed notifications (scheduled_time < now, not delivered)
  2. Mark missed notifications in database
  3. Verify future notifications are scheduled
  4. Reschedule missing future notifications

Platform Reference: iOS §3.1.1


2.2 FORCE_STOP → App Terminated by System

Android Definition:

  • User force-stopped app via Settings
  • All alarms cleared
  • Database still populated
  • Boot receiver blocked until user launches app

iOS Equivalent:

  • App terminated by system (low memory, etc.)
  • Notifications may be missing (system cleared them)
  • Database still populated
  • No user-facing force stop equivalent

Key Difference: iOS doesn't have a user-facing "force stop" option. System termination is the closest equivalent.

Detection Logic:

Android:

// Check if alarms exist
val alarmsExist = alarmManager.hasAlarm(pendingIntent)
if (!alarmsExist && dbHasSchedules && !isBootRecent) {
    return FORCE_STOP
}

iOS:

// Check if notifications exist
let pendingNotifications = try await notificationCenter.pendingNotificationRequests()
let dbSchedules = try database.getEnabledSchedules()

if pendingNotifications.isEmpty && !dbSchedules.isEmpty {
    // DB has schedules but no notifications scheduled
    return .termination
}

Recovery Actions:

  1. Detect all missed notifications
  2. Mark all missed notifications in database
  3. Reschedule all future notifications
  4. Reschedule all fetch schedules (if applicable)

Platform Reference: iOS §3.2.1


2.3 BOOT → Device Reboot

Android Definition:

  • Device rebooted
  • All alarms wiped (OS behavior)
  • Database still populated
  • Boot receiver executes after boot completes

iOS Equivalent:

  • Device rebooted
  • Notifications persist automatically (OS-guaranteed)
  • Database still populated
  • BGTaskScheduler may execute (system-controlled)

Key Difference: iOS automatically persists notifications across reboot. Android requires manual rescheduling.

Detection Logic:

Android:

// Check boot flag (set by BootReceiver)
val bootFlag = sharedPreferences.getLong("last_boot_time", 0)
val currentTime = System.currentTimeMillis()
if (bootFlag > 0 && (currentTime - bootFlag) < 60000) {
    return BOOT
}

iOS:

// BGTaskScheduler registration handles boot
// Check if this is a boot-triggered background task
if isBootBackgroundTask {
    return .boot
}

// Or detect on app launch after reboot
let lastLaunchTime = UserDefaults.standard.double(forKey: "last_launch_time")
let bootTime = ProcessInfo.processInfo.systemUptime
if lastLaunchTime > 0 && bootTime < 60 {
    return .boot
}

Recovery Actions:

  1. Verify notifications still exist (iOS usually handles this)
  2. Detect any missed notifications during reboot window
  3. Reschedule any missing notifications
  4. Update next run times for repeating schedules

Platform Reference: iOS §3.1.2


2.4 WARM_START → App Resume (Foreground)

Android Definition:

  • App resumed from background
  • Alarms still exist
  • Database matches alarm state
  • No recovery needed (optimization)

iOS Equivalent:

  • App resumed from background
  • Notifications still exist
  • Database matches notification state
  • No recovery needed (optimization)

Detection Logic:

Android:

// Check if alarms exist and match DB
val alarmsExist = alarmManager.hasAlarm(pendingIntent)
if (alarmsExist && dbMatchesAlarms) {
    return WARM_START
}

iOS:

// Check if notifications exist and match DB
let pendingNotifications = try await notificationCenter.pendingNotificationRequests()
let dbSchedules = try database.getEnabledSchedules()

let dbIds = Set(dbSchedules.flatMap { $0.getScheduledNotificationIds() })
let pendingIds = Set(pendingNotifications.map { $0.identifier })

if dbIds == pendingIds {
    return .warmStart  // Match indicates warm resume
}

Recovery Actions:

  • None (optimization only)
  • May perform lightweight verification
  • May update metrics

2.5 NONE → First Launch / No Recovery

Android Definition:

  • First app launch
  • Empty database
  • No schedules configured
  • No recovery needed

iOS Equivalent:

  • First app launch
  • Empty database
  • No schedules configured
  • No recovery needed

Detection Logic:

Android:

// Check if database is empty
val schedules = database.scheduleDao().getEnabled()
if (schedules.isEmpty()) {
    return NONE
}

iOS:

// Check if database is empty
let schedules = try database.getEnabledSchedules()
if schedules.isEmpty {
    return .none
}

Recovery Actions:

  • None

3. Recovery Action Mapping

3.1 Missed Notification Detection

Android:

  • Query AlarmManager for past alarms
  • Check database for undelivered notifications
  • Mark as missed in database

iOS:

  • Query database for past scheduled notifications
  • Check delivery status
  • Mark as missed in database

Key Difference: iOS cannot query past notifications from UNUserNotificationCenter. Must rely on database state.

3.2 Future Notification Verification

Android:

  • Query AlarmManager for future alarms
  • Compare with database schedules
  • Reschedule missing alarms

iOS:

  • Query UNUserNotificationCenter for pending notifications
  • Compare with database schedules
  • Reschedule missing notifications

Key Difference: iOS uses UNUserNotificationCenter instead of AlarmManager.

3.3 Full Recovery

Android:

  • Reschedule all notify schedules
  • Reschedule all fetch schedules (WorkManager)
  • Mark past notifications as missed

iOS:

  • Reschedule all notify schedules
  • Reschedule all fetch schedules (BGTaskScheduler)
  • Mark past notifications as missed

Key Difference: iOS uses BGTaskScheduler instead of WorkManager.


4. Implementation Checklist

4.1 Phase 1: Cold Start Recovery

  • Implement scenario detection (cold start)
  • Implement missed notification detection
  • Implement future notification verification
  • Test cold start recovery

4.2 Phase 2: Termination Detection

  • Implement termination detection
  • Implement full recovery logic
  • Test termination recovery

4.3 Phase 3: Boot Recovery

  • Implement BGTaskScheduler registration
  • Implement boot detection
  • Test boot recovery

5. Platform-Specific Notes

5.1 iOS Advantages

  1. Notification Persistence: iOS automatically persists notifications across termination and reboot
  2. No Force Stop: iOS doesn't have user-facing force stop, reducing complexity
  3. Simplified Recovery: Less recovery needed due to OS persistence

5.2 iOS Challenges

  1. No Code Execution on Fire: App code doesn't run when notification fires
  2. Background Limits: Severely limited background execution
  3. Timing Tolerance: ±180 second tolerance for calendar triggers

5.3 Android Advantages

  1. Code Execution on Fire: PendingIntent can execute code when alarm fires
  2. WorkManager: More reliable background execution
  3. Exact Timing: Can achieve exact timing with permission

5.4 Android Challenges

  1. No Persistence: Alarms don't persist across reboot
  2. Force Stop: Hard kill that cannot be bypassed
  3. Boot Recovery: Must implement boot receiver

6. Testing Strategy

6.1 Scenario Testing

Cold Start:

  1. Terminate app (swipe away)
  2. Wait for notification time to pass
  3. Launch app
  4. Verify missed notification detection
  5. Verify future notifications rescheduled

Termination:

  1. Schedule notifications
  2. Terminate app
  3. Clear notifications (simulate system clearing)
  4. Launch app
  5. Verify full recovery

Boot:

  1. Schedule notifications
  2. Reboot device (or simulate)
  3. Launch app
  4. Verify notifications still exist
  5. Verify any missed notifications detected

7. References


Document Version: 1.0.0
Last Updated: 2025-12-08
Next Review: After Phase 1 implementation