You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
381 lines
12 KiB
381 lines
12 KiB
/**
|
|
* DailyNotificationRebootRecoveryManager.java
|
|
*
|
|
* Android Reboot Recovery Manager for notification restoration
|
|
* Handles system reboots and time changes to restore scheduled notifications
|
|
*
|
|
* @author Matthew Raymer
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
package com.timesafari.dailynotification;
|
|
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.os.Build;
|
|
import android.util.Log;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
/**
|
|
* Manages recovery from system reboots and time changes
|
|
*
|
|
* This class implements the critical recovery functionality:
|
|
* - Listens for system reboot broadcasts
|
|
* - Handles time change events
|
|
* - Restores scheduled notifications after reboot
|
|
* - Adjusts notification times after time changes
|
|
*/
|
|
public class DailyNotificationRebootRecoveryManager {
|
|
|
|
// MARK: - Constants
|
|
|
|
private static final String TAG = "DailyNotificationRebootRecoveryManager";
|
|
|
|
// Broadcast actions
|
|
private static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
|
|
private static final String ACTION_MY_PACKAGE_REPLACED = "android.intent.action.MY_PACKAGE_REPLACED";
|
|
private static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED";
|
|
private static final String ACTION_TIME_CHANGED = "android.intent.action.TIME_SET";
|
|
private static final String ACTION_TIMEZONE_CHANGED = "android.intent.action.TIMEZONE_CHANGED";
|
|
|
|
// Recovery delay
|
|
private static final long RECOVERY_DELAY_MS = TimeUnit.SECONDS.toMillis(5);
|
|
|
|
// MARK: - Properties
|
|
|
|
private final Context context;
|
|
private final DailyNotificationScheduler scheduler;
|
|
private final DailyNotificationExactAlarmManager exactAlarmManager;
|
|
private final DailyNotificationRollingWindow rollingWindow;
|
|
|
|
// Broadcast receivers
|
|
private BootCompletedReceiver bootCompletedReceiver;
|
|
private TimeChangeReceiver timeChangeReceiver;
|
|
|
|
// Recovery state
|
|
private boolean recoveryInProgress = false;
|
|
private long lastRecoveryTime = 0;
|
|
|
|
// MARK: - Initialization
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param context Application context
|
|
* @param scheduler Notification scheduler
|
|
* @param exactAlarmManager Exact alarm manager
|
|
* @param rollingWindow Rolling window manager
|
|
*/
|
|
public DailyNotificationRebootRecoveryManager(Context context,
|
|
DailyNotificationScheduler scheduler,
|
|
DailyNotificationExactAlarmManager exactAlarmManager,
|
|
DailyNotificationRollingWindow rollingWindow) {
|
|
this.context = context;
|
|
this.scheduler = scheduler;
|
|
this.exactAlarmManager = exactAlarmManager;
|
|
this.rollingWindow = rollingWindow;
|
|
|
|
Log.d(TAG, "RebootRecoveryManager initialized");
|
|
}
|
|
|
|
/**
|
|
* Register broadcast receivers
|
|
*/
|
|
public void registerReceivers() {
|
|
try {
|
|
Log.d(TAG, "Registering broadcast receivers");
|
|
|
|
// Register boot completed receiver
|
|
bootCompletedReceiver = new BootCompletedReceiver();
|
|
IntentFilter bootFilter = new IntentFilter();
|
|
bootFilter.addAction(ACTION_BOOT_COMPLETED);
|
|
bootFilter.addAction(ACTION_MY_PACKAGE_REPLACED);
|
|
bootFilter.addAction(ACTION_PACKAGE_REPLACED);
|
|
context.registerReceiver(bootCompletedReceiver, bootFilter);
|
|
|
|
// Register time change receiver
|
|
timeChangeReceiver = new TimeChangeReceiver();
|
|
IntentFilter timeFilter = new IntentFilter();
|
|
timeFilter.addAction(ACTION_TIME_CHANGED);
|
|
timeFilter.addAction(ACTION_TIMEZONE_CHANGED);
|
|
context.registerReceiver(timeChangeReceiver, timeFilter);
|
|
|
|
Log.i(TAG, "Broadcast receivers registered successfully");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error registering broadcast receivers", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unregister broadcast receivers
|
|
*/
|
|
public void unregisterReceivers() {
|
|
try {
|
|
Log.d(TAG, "Unregistering broadcast receivers");
|
|
|
|
if (bootCompletedReceiver != null) {
|
|
context.unregisterReceiver(bootCompletedReceiver);
|
|
bootCompletedReceiver = null;
|
|
}
|
|
|
|
if (timeChangeReceiver != null) {
|
|
context.unregisterReceiver(timeChangeReceiver);
|
|
timeChangeReceiver = null;
|
|
}
|
|
|
|
Log.i(TAG, "Broadcast receivers unregistered successfully");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error unregistering broadcast receivers", e);
|
|
}
|
|
}
|
|
|
|
// MARK: - Recovery Methods
|
|
|
|
/**
|
|
* Handle system reboot recovery
|
|
*
|
|
* This method restores all scheduled notifications that were lost
|
|
* during the system reboot.
|
|
*/
|
|
public void handleSystemReboot() {
|
|
try {
|
|
Log.i(TAG, "Handling system reboot recovery");
|
|
|
|
// Check if recovery is already in progress
|
|
if (recoveryInProgress) {
|
|
Log.w(TAG, "Recovery already in progress, skipping");
|
|
return;
|
|
}
|
|
|
|
// Check if recovery was recently performed
|
|
long currentTime = System.currentTimeMillis();
|
|
if (currentTime - lastRecoveryTime < RECOVERY_DELAY_MS) {
|
|
Log.w(TAG, "Recovery performed recently, skipping");
|
|
return;
|
|
}
|
|
|
|
recoveryInProgress = true;
|
|
lastRecoveryTime = currentTime;
|
|
|
|
// Perform recovery operations
|
|
performRebootRecovery();
|
|
|
|
recoveryInProgress = false;
|
|
|
|
Log.i(TAG, "System reboot recovery completed");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error handling system reboot", e);
|
|
recoveryInProgress = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle time change recovery
|
|
*
|
|
* This method adjusts all scheduled notifications to account
|
|
* for system time changes.
|
|
*/
|
|
public void handleTimeChange() {
|
|
try {
|
|
Log.i(TAG, "Handling time change recovery");
|
|
|
|
// Check if recovery is already in progress
|
|
if (recoveryInProgress) {
|
|
Log.w(TAG, "Recovery already in progress, skipping");
|
|
return;
|
|
}
|
|
|
|
recoveryInProgress = true;
|
|
|
|
// Perform time change recovery
|
|
performTimeChangeRecovery();
|
|
|
|
recoveryInProgress = false;
|
|
|
|
Log.i(TAG, "Time change recovery completed");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error handling time change", e);
|
|
recoveryInProgress = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform reboot recovery operations
|
|
*/
|
|
private void performRebootRecovery() {
|
|
try {
|
|
Log.d(TAG, "Performing reboot recovery operations");
|
|
|
|
// Wait a bit for system to stabilize
|
|
Thread.sleep(2000);
|
|
|
|
// Restore scheduled notifications
|
|
scheduler.restoreScheduledNotifications();
|
|
|
|
// Restore rolling window
|
|
rollingWindow.forceMaintenance();
|
|
|
|
// Log recovery statistics
|
|
logRecoveryStatistics("reboot");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error performing reboot recovery", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform time change recovery operations
|
|
*/
|
|
private void performTimeChangeRecovery() {
|
|
try {
|
|
Log.d(TAG, "Performing time change recovery operations");
|
|
|
|
// Adjust scheduled notifications
|
|
scheduler.adjustScheduledNotifications();
|
|
|
|
// Update rolling window
|
|
rollingWindow.forceMaintenance();
|
|
|
|
// Log recovery statistics
|
|
logRecoveryStatistics("time_change");
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error performing time change recovery", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Log recovery statistics
|
|
*
|
|
* @param recoveryType Type of recovery performed
|
|
*/
|
|
private void logRecoveryStatistics(String recoveryType) {
|
|
try {
|
|
// Get recovery statistics
|
|
int restoredCount = scheduler.getRestoredNotificationCount();
|
|
int adjustedCount = scheduler.getAdjustedNotificationCount();
|
|
|
|
Log.i(TAG, String.format("Recovery statistics (%s): restored=%d, adjusted=%d",
|
|
recoveryType, restoredCount, adjustedCount));
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error logging recovery statistics", e);
|
|
}
|
|
}
|
|
|
|
// MARK: - Broadcast Receivers
|
|
|
|
/**
|
|
* Broadcast receiver for boot completed events
|
|
*/
|
|
private class BootCompletedReceiver extends BroadcastReceiver {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
try {
|
|
String action = intent.getAction();
|
|
Log.d(TAG, "BootCompletedReceiver received action: " + action);
|
|
|
|
if (ACTION_BOOT_COMPLETED.equals(action) ||
|
|
ACTION_MY_PACKAGE_REPLACED.equals(action) ||
|
|
ACTION_PACKAGE_REPLACED.equals(action)) {
|
|
|
|
// Handle system reboot
|
|
handleSystemReboot();
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error in BootCompletedReceiver", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Broadcast receiver for time change events
|
|
*/
|
|
private class TimeChangeReceiver extends BroadcastReceiver {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
try {
|
|
String action = intent.getAction();
|
|
Log.d(TAG, "TimeChangeReceiver received action: " + action);
|
|
|
|
if (ACTION_TIME_CHANGED.equals(action) ||
|
|
ACTION_TIMEZONE_CHANGED.equals(action)) {
|
|
|
|
// Handle time change
|
|
handleTimeChange();
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
Log.e(TAG, "Error in TimeChangeReceiver", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Public Methods
|
|
|
|
/**
|
|
* Get recovery status
|
|
*
|
|
* @return Recovery status information
|
|
*/
|
|
public RecoveryStatus getRecoveryStatus() {
|
|
return new RecoveryStatus(
|
|
recoveryInProgress,
|
|
lastRecoveryTime,
|
|
System.currentTimeMillis() - lastRecoveryTime
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Force recovery (for testing)
|
|
*/
|
|
public void forceRecovery() {
|
|
Log.i(TAG, "Forcing recovery");
|
|
handleSystemReboot();
|
|
}
|
|
|
|
/**
|
|
* Check if recovery is needed
|
|
*
|
|
* @return true if recovery is needed
|
|
*/
|
|
public boolean isRecoveryNeeded() {
|
|
// Check if system was recently rebooted
|
|
long currentTime = System.currentTimeMillis();
|
|
long timeSinceLastRecovery = currentTime - lastRecoveryTime;
|
|
|
|
// Recovery needed if more than 1 hour since last recovery
|
|
return timeSinceLastRecovery > TimeUnit.HOURS.toMillis(1);
|
|
}
|
|
|
|
// MARK: - Status Classes
|
|
|
|
/**
|
|
* Recovery status information
|
|
*/
|
|
public static class RecoveryStatus {
|
|
public final boolean inProgress;
|
|
public final long lastRecoveryTime;
|
|
public final long timeSinceLastRecovery;
|
|
|
|
public RecoveryStatus(boolean inProgress, long lastRecoveryTime, long timeSinceLastRecovery) {
|
|
this.inProgress = inProgress;
|
|
this.lastRecoveryTime = lastRecoveryTime;
|
|
this.timeSinceLastRecovery = timeSinceLastRecovery;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return String.format("RecoveryStatus{inProgress=%s, lastRecovery=%d, timeSince=%d}",
|
|
inProgress, lastRecoveryTime, timeSinceLastRecovery);
|
|
}
|
|
}
|
|
}
|
|
|