chore: synch this plan
This commit is contained in:
619
docs/android-implementation-directive-phase3.md
Normal file
619
docs/android-implementation-directive-phase3.md
Normal file
@@ -0,0 +1,619 @@
|
||||
# Android Implementation Directive: Phase 3 - Boot Receiver Missed Alarm Handling
|
||||
|
||||
**Author**: Matthew Raymer
|
||||
**Date**: November 2025
|
||||
**Status**: Phase 3 - Boot Recovery Enhancement
|
||||
**Version**: 1.0.0
|
||||
|
||||
## Purpose
|
||||
|
||||
Phase 3 enhances the **boot receiver** to detect and handle missed alarms during device reboot. This handles alarms that were scheduled before reboot but were missed because alarms are wiped on reboot.
|
||||
|
||||
**Prerequisites**: Phase 1 and Phase 2 must be complete.
|
||||
|
||||
**Scope**: Boot receiver missed alarm detection and handling.
|
||||
|
||||
**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.
|
||||
|
||||
**Boot vs App Launch Recovery**:
|
||||
|
||||
| Scenario | Entry point | Directive | Responsibility |
|
||||
| -------------------------------- | --------------------------------------- | --------- | ---------------------------------------- |
|
||||
| App launch after kill/force-stop | `ReactivationManager.performRecovery()` | Phase 1–2 | Detect & recover missed |
|
||||
| Device reboot | `BootReceiver` → `ReactivationManager` | Phase 3 | Queue recovery, ReactivationManager handles |
|
||||
|
||||
**User-Facing Behavior**: In Phase 3, missed alarms are **recorded** and **rescheduled**, but not yet surfaced to the user with explicit "you missed this" UX (that's a future concern).
|
||||
|
||||
---
|
||||
|
||||
## 1. Acceptance Criteria
|
||||
|
||||
### 1.1 Definition of Done
|
||||
|
||||
**Phase 3 is complete when:**
|
||||
|
||||
1. ✅ **Boot receiver detects missed alarms**
|
||||
- Alarms with `nextRunAt < currentTime` detected during boot recovery
|
||||
- Detection runs automatically on `BOOT_COMPLETED` intent
|
||||
- Detection completes within 5 seconds (boot receiver timeout)
|
||||
|
||||
2. ✅ **Missed alarms are marked in database**
|
||||
- `delivery_status` updated to `'missed'`
|
||||
- `last_delivery_attempt` updated to current time
|
||||
- Status change logged in history table
|
||||
|
||||
3. ✅ **Next occurrence is rescheduled for repeating schedules**
|
||||
- Repeating schedules calculate next occurrence after missed time
|
||||
- Next occurrence scheduled via AlarmManager
|
||||
- Non-repeating schedules not rescheduled
|
||||
|
||||
4. ✅ **Future alarms are rescheduled**
|
||||
- All future alarms (not missed) rescheduled normally
|
||||
- Existing boot receiver logic enhanced, not replaced
|
||||
|
||||
5. ✅ **Boot recovery never crashes**
|
||||
- All exceptions caught and logged
|
||||
- Database errors don't propagate
|
||||
- Invalid data handled gracefully
|
||||
|
||||
### 1.2 Success Metrics
|
||||
|
||||
| Metric | Target | Measurement |
|
||||
|--------|--------|-------------|
|
||||
| Boot receiver execution time | < 5 seconds | Log timestamp difference |
|
||||
| Missed detection accuracy | 100% | Manual verification via logs |
|
||||
| Next occurrence calculation accuracy | 100% | Verify scheduled time matches expected |
|
||||
| Recovery success rate | > 95% | History table outcome field |
|
||||
| Crash rate | 0% | No exceptions propagate |
|
||||
|
||||
### 1.3 Out of Scope (Phase 3)
|
||||
|
||||
- ❌ Warm start optimization (future phase)
|
||||
- ❌ Callback event emission (future phase)
|
||||
- ❌ User notification of missed alarms (future phase)
|
||||
- ❌ Boot receiver performance optimization (future phase)
|
||||
|
||||
---
|
||||
|
||||
## 2. Implementation: BootReceiver Enhancement
|
||||
|
||||
### 2.1 Canonical Source of Truth
|
||||
|
||||
**⚠️ CRITICAL CORRECTION**: BootReceiver must **NOT** implement recovery logic directly. It must **only queue** ReactivationManager.performRecovery() with a BOOT flag.
|
||||
|
||||
**ReactivationManager.kt** is the **only** file allowed to:
|
||||
- Perform scenario detection
|
||||
- Initiate recovery logic
|
||||
- Branch execution per phase
|
||||
|
||||
### 2.2 Update BootReceiver
|
||||
|
||||
**File**: `android/src/main/java/com/timesafari/dailynotification/BootReceiver.kt`
|
||||
|
||||
**Location**: `onReceive()` method
|
||||
|
||||
### 2.3 Corrected Implementation
|
||||
|
||||
**Corrected Code** (BootReceiver only queues recovery):
|
||||
```kotlin
|
||||
package com.timesafari.dailynotification
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Boot recovery receiver to trigger recovery after device reboot
|
||||
* Phase 3: Only queues ReactivationManager, does not implement recovery directly
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
* @version 2.0.0 - Corrected to queue ReactivationManager only
|
||||
*/
|
||||
class BootReceiver : BroadcastReceiver() {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "DNP-BOOT"
|
||||
private const val PREFS_NAME = "dailynotification_recovery"
|
||||
private const val KEY_LAST_BOOT_AT = "last_boot_at"
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent?) {
|
||||
if (intent?.action == Intent.ACTION_BOOT_COMPLETED) {
|
||||
Log.i(TAG, "Boot completed, queuing ReactivationManager recovery")
|
||||
|
||||
// Set boot flag in SharedPreferences
|
||||
// ReactivationManager will detect this and handle recovery
|
||||
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
prefs.edit().putLong(KEY_LAST_BOOT_AT, System.currentTimeMillis()).apply()
|
||||
|
||||
// Queue ReactivationManager recovery
|
||||
// Recovery will run when app launches or can be triggered immediately
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val reactivationManager = ReactivationManager(context)
|
||||
reactivationManager.performRecovery()
|
||||
Log.i(TAG, "Boot recovery queued to ReactivationManager")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to queue boot recovery", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**⚠️ REMOVED**: All direct rescheduling logic from BootReceiver. Recovery is now handled entirely by ReactivationManager.
|
||||
|
||||
### 2.4 How Boot Recovery Works
|
||||
|
||||
**Flow**:
|
||||
1. Device reboots → `BootReceiver.onReceive()` called
|
||||
2. BootReceiver sets `last_boot_at` flag in SharedPreferences
|
||||
3. BootReceiver queues `ReactivationManager.performRecovery()`
|
||||
4. ReactivationManager detects BOOT scenario via `isBootRecovery()`
|
||||
5. ReactivationManager handles recovery (same logic as force stop - all alarms wiped)
|
||||
|
||||
**Key Points**:
|
||||
- BootReceiver **never** implements recovery directly
|
||||
- All recovery logic is in ReactivationManager
|
||||
- Boot recovery uses same recovery path as force stop (all alarms wiped on reboot)
|
||||
|
||||
---
|
||||
|
||||
## 3. Data Integrity Checks
|
||||
```kotlin
|
||||
/**
|
||||
* Reschedule notifications after device reboot
|
||||
* Phase 3: Adds missed alarm detection and handling
|
||||
*
|
||||
* @param context Application context
|
||||
*/
|
||||
private suspend fun rescheduleNotifications(context: Context) {
|
||||
val db = DailyNotificationDatabase.getDatabase(context)
|
||||
val enabledSchedules = db.scheduleDao().getEnabled()
|
||||
val currentTime = System.currentTimeMillis()
|
||||
|
||||
Log.i(TAG, "Boot recovery: Found ${enabledSchedules.size} enabled schedules to reschedule")
|
||||
|
||||
var futureRescheduled = 0
|
||||
var missedDetected = 0
|
||||
var missedRescheduled = 0
|
||||
var errors = 0
|
||||
|
||||
enabledSchedules.forEach { schedule ->
|
||||
try {
|
||||
when (schedule.kind) {
|
||||
"fetch" -> {
|
||||
// Reschedule WorkManager fetch (unchanged)
|
||||
val config = ContentFetchConfig(
|
||||
enabled = schedule.enabled,
|
||||
schedule = schedule.cron ?: schedule.clockTime ?: "0 9 * * *",
|
||||
url = null,
|
||||
timeout = 30000,
|
||||
retryAttempts = 3,
|
||||
retryDelay = 1000,
|
||||
callbacks = CallbackConfig()
|
||||
)
|
||||
FetchWorker.scheduleFetch(context, config)
|
||||
Log.i(TAG, "Rescheduled fetch for schedule: ${schedule.id}")
|
||||
futureRescheduled++
|
||||
}
|
||||
"notify" -> {
|
||||
// Phase 3: Handle both past and future alarms
|
||||
val nextRunTime = calculateNextRunTime(schedule)
|
||||
|
||||
if (nextRunTime > currentTime) {
|
||||
// Future alarm - reschedule normally
|
||||
val config = UserNotificationConfig(
|
||||
enabled = schedule.enabled,
|
||||
schedule = schedule.cron ?: schedule.clockTime ?: "0 9 * * *",
|
||||
title = "Daily Notification",
|
||||
body = "Your daily update is ready",
|
||||
sound = true,
|
||||
vibration = true,
|
||||
priority = "normal"
|
||||
)
|
||||
NotifyReceiver.scheduleExactNotification(context, nextRunTime, config)
|
||||
Log.i(TAG, "Rescheduled future notification: ${schedule.id} for $nextRunTime")
|
||||
futureRescheduled++
|
||||
} else {
|
||||
// Past alarm - was missed during reboot
|
||||
missedDetected++
|
||||
Log.i(TAG, "Missed alarm detected on boot: ${schedule.id} scheduled for $nextRunTime")
|
||||
|
||||
// Mark as missed
|
||||
handleMissedAlarmOnBoot(context, schedule, nextRunTime, db)
|
||||
|
||||
// Reschedule next occurrence if repeating
|
||||
if (isRepeating(schedule)) {
|
||||
try {
|
||||
val nextOccurrence = calculateNextOccurrence(schedule, currentTime)
|
||||
val config = UserNotificationConfig(
|
||||
enabled = schedule.enabled,
|
||||
schedule = schedule.cron ?: schedule.clockTime ?: "0 9 * * *",
|
||||
title = "Daily Notification",
|
||||
body = "Your daily update is ready",
|
||||
sound = true,
|
||||
vibration = true,
|
||||
priority = "normal"
|
||||
)
|
||||
NotifyReceiver.scheduleExactNotification(context, nextOccurrence, config)
|
||||
Log.i(TAG, "Rescheduled next occurrence for missed alarm: ${schedule.id} for $nextOccurrence")
|
||||
missedRescheduled++
|
||||
} catch (e: Exception) {
|
||||
errors++
|
||||
Log.e(TAG, "Failed to reschedule next occurrence: ${schedule.id}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Log.w(TAG, "Unknown schedule kind: ${schedule.kind}")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
errors++
|
||||
Log.e(TAG, "Failed to reschedule ${schedule.kind} for ${schedule.id}", e)
|
||||
}
|
||||
}
|
||||
|
||||
// Record boot recovery in history
|
||||
try {
|
||||
db.historyDao().insert(
|
||||
History(
|
||||
refId = "boot_recovery_${System.currentTimeMillis()}",
|
||||
kind = "boot_recovery",
|
||||
occurredAt = System.currentTimeMillis(),
|
||||
outcome = if (errors == 0) "success" else "partial",
|
||||
diagJson = """
|
||||
{
|
||||
"schedules_rescheduled": $futureRescheduled,
|
||||
"missed_detected": $missedDetected,
|
||||
"missed_rescheduled": $missedRescheduled,
|
||||
"errors": $errors
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to record boot recovery history", e)
|
||||
// Don't fail boot recovery if history recording fails
|
||||
}
|
||||
|
||||
Log.i(TAG, "Boot recovery complete: $futureRescheduled future, $missedDetected missed, $missedRescheduled next occurrences, $errors errors")
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: All data integrity checks are handled by ReactivationManager (Phase 2). BootReceiver does not perform any data operations directly.
|
||||
|
||||
### 3.1 Missed Alarm Detection Validation
|
||||
|
||||
```kotlin
|
||||
/**
|
||||
* Handle missed alarm detected during boot recovery
|
||||
* Phase 3: Marks missed alarm in database
|
||||
*
|
||||
* @param context Application context
|
||||
* @param schedule Schedule that was missed
|
||||
* @param scheduledTime When the alarm was scheduled
|
||||
* @param db Database instance
|
||||
*/
|
||||
private suspend fun handleMissedAlarmOnBoot(
|
||||
context: Context,
|
||||
schedule: Schedule,
|
||||
scheduledTime: Long,
|
||||
db: DailyNotificationDatabase
|
||||
) {
|
||||
try {
|
||||
// Data integrity check
|
||||
if (schedule.id.isBlank()) {
|
||||
Log.w(TAG, "Skipping invalid schedule: empty ID")
|
||||
return
|
||||
}
|
||||
|
||||
// Try to find existing NotificationContentEntity
|
||||
val notificationId = schedule.id
|
||||
val existingNotification = db.notificationContentDao().getNotificationById(notificationId)
|
||||
|
||||
if (existingNotification != null) {
|
||||
// Update existing notification
|
||||
existingNotification.deliveryStatus = "missed"
|
||||
existingNotification.lastDeliveryAttempt = System.currentTimeMillis()
|
||||
existingNotification.deliveryAttempts = (existingNotification.deliveryAttempts ?: 0) + 1
|
||||
db.notificationContentDao().updateNotification(existingNotification)
|
||||
Log.d(TAG, "Marked missed notification on boot: $notificationId")
|
||||
} else {
|
||||
// No NotificationContentEntity found - this is okay for boot recovery
|
||||
// The schedule exists but content may not have been fetched yet
|
||||
Log.d(TAG, "No NotificationContentEntity found for missed schedule: $notificationId (expected for boot recovery)")
|
||||
}
|
||||
|
||||
// Record missed alarm in history
|
||||
try {
|
||||
db.historyDao().insert(
|
||||
History(
|
||||
refId = "missed_boot_${schedule.id}_${System.currentTimeMillis()}",
|
||||
kind = "missed_alarm",
|
||||
occurredAt = System.currentTimeMillis(),
|
||||
outcome = "missed",
|
||||
diagJson = """
|
||||
{
|
||||
"schedule_id": "${schedule.id}",
|
||||
"scheduled_time": $scheduledTime,
|
||||
"detected_at": ${System.currentTimeMillis()},
|
||||
"scenario": "boot_recovery"
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to record missed alarm history", e)
|
||||
// Don't fail if history recording fails
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to handle missed alarm on boot: ${schedule.id}", e)
|
||||
// Don't throw - continue with boot recovery
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 Helper Methods
|
||||
|
||||
**⚠️ Implementation Consistency**: These helpers must match the implementation used in `ReactivationManager` (Phase 2). Treat ReactivationManager as canonical and keep these in sync.
|
||||
|
||||
```kotlin
|
||||
/**
|
||||
* Check if schedule is repeating
|
||||
*
|
||||
* **Implementation Note**: Must match `isRepeating()` in ReactivationManager (Phase 2).
|
||||
* This is a duplication for now; treat ReactivationManager as canonical.
|
||||
*
|
||||
* @param schedule Schedule to check
|
||||
* @return true if repeating, false if one-time
|
||||
*/
|
||||
private fun isRepeating(schedule: Schedule): Boolean {
|
||||
// Schedules with cron or clockTime are repeating
|
||||
return schedule.cron != null || schedule.clockTime != null
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate next occurrence for repeating schedule
|
||||
*
|
||||
* **Implementation Note**: Must match `calculateNextOccurrence()` in ReactivationManager (Phase 2).
|
||||
* This is a duplication for now; treat ReactivationManager as canonical.
|
||||
*
|
||||
* @param schedule Schedule to calculate for
|
||||
* @param fromTime Calculate next occurrence after this time
|
||||
* @return Next occurrence time in milliseconds
|
||||
*/
|
||||
private fun calculateNextOccurrence(schedule: Schedule, fromTime: Long): Long {
|
||||
// TODO: Implement proper calculation based on cron/clockTime
|
||||
// For now, simplified: daily schedules add 24 hours
|
||||
// This should match the logic in ReactivationManager (Phase 2)
|
||||
return when {
|
||||
schedule.cron != null -> {
|
||||
// Parse cron and calculate next occurrence
|
||||
// For now, simplified: daily schedules
|
||||
fromTime + (24 * 60 * 60 * 1000L)
|
||||
}
|
||||
schedule.clockTime != null -> {
|
||||
// Parse HH:mm and calculate next occurrence
|
||||
// For now, simplified: daily schedules
|
||||
fromTime + (24 * 60 * 60 * 1000L)
|
||||
}
|
||||
else -> {
|
||||
// Not repeating
|
||||
fromTime
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Data Integrity Checks
|
||||
|
||||
### 3.1 Missed Alarm Detection Validation
|
||||
|
||||
**Boot Flag Rules**:
|
||||
- ✅ BootReceiver sets flag immediately on BOOT_COMPLETED
|
||||
- ✅ Flag is valid for 60 seconds after boot
|
||||
- ✅ ReactivationManager clears flag after reading
|
||||
- ✅ Stale flags are ignored (prevents false positives)
|
||||
|
||||
**Edge Cases**:
|
||||
- ✅ Multiple boot broadcasts: Flag is overwritten (last one wins)
|
||||
- ✅ App not launched after boot: Flag expires after 60 seconds
|
||||
- ✅ SharedPreferences errors: Log error, recovery continues
|
||||
|
||||
---
|
||||
|
||||
## 4. Rollback Safety
|
||||
|
||||
### 4.1 No-Crash Guarantee
|
||||
|
||||
**All boot recovery operations must:**
|
||||
|
||||
1. **Catch all exceptions** - Never propagate exceptions
|
||||
2. **Continue processing** - One schedule failure doesn't stop recovery
|
||||
3. **Log errors** - All failures logged with context
|
||||
4. **Timeout protection** - Boot receiver has 10-second timeout (Android limit)
|
||||
|
||||
### 4.2 Error Handling Strategy
|
||||
|
||||
| Error Type | Handling | Log Level |
|
||||
|------------|----------|-----------|
|
||||
| Schedule query failure | Return empty list, log error | ERROR |
|
||||
| Invalid schedule data | Skip schedule, continue | WARN |
|
||||
| Missed alarm marking failure | Log error, continue | ERROR |
|
||||
| Next occurrence calculation failure | Log error, don't reschedule | ERROR |
|
||||
| Alarm reschedule failure | Log error, continue | ERROR |
|
||||
| History recording failure | Log warning, don't fail | WARN |
|
||||
|
||||
---
|
||||
|
||||
## 5. Testing Requirements
|
||||
|
||||
### 5.1 Test 1: Boot Recovery Missed Detection
|
||||
|
||||
**Purpose**: Verify boot receiver detects missed alarms.
|
||||
|
||||
**Steps**:
|
||||
1. Schedule notification for 5 minutes in future
|
||||
2. Verify alarm scheduled: `adb shell dumpsys alarm | grep timesafari`
|
||||
3. Reboot device: `adb reboot`
|
||||
4. Wait for boot: `adb wait-for-device && adb shell getprop sys.boot_completed` (wait for "1")
|
||||
5. Wait 10 minutes (past scheduled time)
|
||||
6. Check boot logs: `adb logcat -d | grep DNP-BOOT`
|
||||
|
||||
**Expected**:
|
||||
- ✅ Log shows "Boot recovery: Found X enabled schedules"
|
||||
- ✅ Log shows "Missed alarm detected on boot: <id>"
|
||||
- ✅ Database shows `delivery_status = 'missed'`
|
||||
|
||||
**Pass Criteria**: Missed alarm detected during boot recovery.
|
||||
|
||||
### 5.2 Test 2: Next Occurrence Rescheduling
|
||||
|
||||
**Purpose**: Verify repeating schedules calculate next occurrence correctly.
|
||||
|
||||
**Steps**:
|
||||
1. Schedule daily notification (cron: "0 9 * * *") for today at 9 AM
|
||||
2. Reboot device
|
||||
3. Wait until 10 AM (past scheduled time)
|
||||
4. Check boot logs
|
||||
5. Verify next occurrence scheduled: `adb shell dumpsys alarm | grep timesafari`
|
||||
|
||||
**Expected**:
|
||||
- ✅ Log shows "Missed alarm detected on boot"
|
||||
- ✅ Log shows "Rescheduled next occurrence for missed alarm"
|
||||
- ✅ AlarmManager shows alarm scheduled for tomorrow 9 AM
|
||||
|
||||
**Pass Criteria**: Next occurrence correctly calculated and scheduled.
|
||||
|
||||
### 5.3 Test 3: Future Alarm Rescheduling
|
||||
|
||||
**Purpose**: Verify future alarms are still rescheduled normally.
|
||||
|
||||
**Steps**:
|
||||
1. Schedule notification for 1 hour in future
|
||||
2. Reboot device
|
||||
3. Wait for boot
|
||||
4. Verify alarm rescheduled: `adb shell dumpsys alarm | grep timesafari`
|
||||
|
||||
**Expected**:
|
||||
- ✅ Log shows "Rescheduled future notification"
|
||||
- ✅ AlarmManager shows alarm scheduled for original time
|
||||
- ✅ No missed alarm detection for future alarms
|
||||
|
||||
**Pass Criteria**: Future alarms rescheduled normally.
|
||||
|
||||
### 5.4 Test 4: Non-Repeating Schedule Handling
|
||||
|
||||
**Purpose**: Verify non-repeating schedules don't reschedule next occurrence.
|
||||
|
||||
**Steps**:
|
||||
1. Schedule one-time notification (no cron/clockTime) for 5 minutes in future
|
||||
2. Reboot device
|
||||
3. Wait 10 minutes (past scheduled time)
|
||||
4. Check boot logs
|
||||
5. Verify no next occurrence scheduled
|
||||
|
||||
**Expected**:
|
||||
- ✅ Log shows "Missed alarm detected on boot"
|
||||
- ✅ Log does NOT show "Rescheduled next occurrence"
|
||||
- ✅ AlarmManager does NOT show new alarm
|
||||
|
||||
**Pass Criteria**: Non-repeating schedules don't reschedule.
|
||||
|
||||
### 5.5 Test 5: Boot Recovery Error Handling
|
||||
|
||||
**Purpose**: Verify boot recovery handles errors gracefully.
|
||||
|
||||
**Steps**:
|
||||
1. Manually corrupt database (insert invalid schedule)
|
||||
2. Reboot device
|
||||
3. Check boot logs
|
||||
|
||||
**Expected**:
|
||||
- ✅ Invalid schedule skipped with warning
|
||||
- ✅ Boot recovery continues normally
|
||||
- ✅ Valid schedules still recovered
|
||||
- ✅ No crash or exception
|
||||
|
||||
**Pass Criteria**: Errors handled gracefully, recovery continues.
|
||||
|
||||
---
|
||||
|
||||
## 6. Implementation Checklist
|
||||
|
||||
- [ ] Update `BootReceiver.onReceive()` to set boot flag
|
||||
- [ ] Update `BootReceiver.onReceive()` to queue ReactivationManager
|
||||
- [ ] Remove all direct rescheduling logic from BootReceiver
|
||||
- [ ] Verify ReactivationManager detects BOOT scenario correctly
|
||||
- [ ] Update history recording to include missed alarm counts
|
||||
- [ ] Add data integrity checks
|
||||
- [ ] Add error handling
|
||||
- [ ] Test boot recovery missed detection
|
||||
- [ ] Test next occurrence rescheduling
|
||||
- [ ] Test future alarm rescheduling
|
||||
- [ ] Test non-repeating schedule handling
|
||||
- [ ] Test error handling
|
||||
- [ ] Verify no duplicate alarms
|
||||
|
||||
---
|
||||
|
||||
## 7. Code References
|
||||
|
||||
**Existing Code to Reuse**:
|
||||
- `BootReceiver.rescheduleNotifications()` - Line 38 (update existing)
|
||||
- `BootReceiver.calculateNextRunTime()` - Line 103 (already exists)
|
||||
- `NotifyReceiver.scheduleExactNotification()` - Line 92
|
||||
- `ScheduleDao.getEnabled()` - Line 298
|
||||
- `NotificationContentDao.getNotificationById()` - Line 69
|
||||
|
||||
**New Code to Create**:
|
||||
- `handleMissedAlarmOnBoot()` - Add to BootReceiver
|
||||
- `isRepeating()` - Add to BootReceiver (or reuse from ReactivationManager)
|
||||
- `calculateNextOccurrence()` - Add to BootReceiver (or reuse from ReactivationManager)
|
||||
|
||||
---
|
||||
|
||||
## 8. Success Criteria Summary
|
||||
|
||||
**Phase 3 is complete when:**
|
||||
|
||||
1. ✅ Boot receiver detects missed alarms
|
||||
2. ✅ Missed alarms marked in database
|
||||
3. ✅ Next occurrence rescheduled for repeating schedules
|
||||
4. ✅ Future alarms rescheduled normally
|
||||
5. ✅ Boot recovery never crashes
|
||||
6. ✅ All tests pass
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Phase 1: Cold Start Recovery](./android-implementation-directive-phase1.md) - Prerequisite
|
||||
- [Phase 2: Force Stop Recovery](./android-implementation-directive-phase2.md) - Prerequisite
|
||||
- [Full Implementation Directive](./android-implementation-directive.md) - Complete scope
|
||||
- [Exploration Findings](./exploration-findings-initial.md) - Gap analysis
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **Prerequisites**: Phase 1 and Phase 2 must be complete before starting Phase 3
|
||||
- **Boot receiver timeout**: Android limits boot receiver execution to 10 seconds
|
||||
- **Comprehensive recovery**: Boot recovery handles both missed and future alarms
|
||||
- **Safety first**: All recovery operations are non-blocking and non-fatal
|
||||
- **Code reuse**: Consider extracting helper methods to shared utility class
|
||||
|
||||
Reference in New Issue
Block a user