diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index fab80d6..a1a4759 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -26,19 +26,26 @@
+ android:exported="false">
+
+
+
+
+ android:exported="true"
+ android:directBootAware="true">
+
+
+
+
-
-
diff --git a/android/plugin/src/main/java/com/timesafari/dailynotification/BootReceiver.java b/android/plugin/src/main/java/com/timesafari/dailynotification/BootReceiver.java
new file mode 100644
index 0000000..483e538
--- /dev/null
+++ b/android/plugin/src/main/java/com/timesafari/dailynotification/BootReceiver.java
@@ -0,0 +1,167 @@
+/**
+ * BootReceiver.java
+ *
+ * Android Boot Receiver for DailyNotification plugin
+ * Handles system boot events 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.util.Log;
+
+/**
+ * Broadcast receiver for system boot events
+ *
+ * This receiver is triggered when:
+ * - Device boots up (BOOT_COMPLETED)
+ * - App is updated (MY_PACKAGE_REPLACED)
+ * - Any package is updated (PACKAGE_REPLACED)
+ *
+ * It ensures that scheduled notifications are restored after system events
+ * that might have cleared the alarm manager.
+ */
+public class BootReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "BootReceiver";
+
+ // Broadcast actions we handle
+ private static final String ACTION_LOCKED_BOOT_COMPLETED = "android.intent.action.LOCKED_BOOT_COMPLETED";
+ 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";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent == null || intent.getAction() == null) {
+ Log.w(TAG, "Received null intent or action");
+ return;
+ }
+
+ String action = intent.getAction();
+ Log.d(TAG, "Received broadcast: " + action);
+
+ try {
+ switch (action) {
+ case ACTION_LOCKED_BOOT_COMPLETED:
+ handleLockedBootCompleted(context);
+ break;
+
+ case ACTION_BOOT_COMPLETED:
+ handleBootCompleted(context);
+ break;
+
+ case ACTION_MY_PACKAGE_REPLACED:
+ handlePackageReplaced(context, intent);
+ break;
+
+ default:
+ Log.w(TAG, "Unknown action: " + action);
+ break;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error handling broadcast: " + action, e);
+ }
+ }
+
+ /**
+ * Handle locked boot completion (before user unlock)
+ *
+ * @param context Application context
+ */
+ private void handleLockedBootCompleted(Context context) {
+ Log.i(TAG, "Locked boot completed - preparing for recovery");
+
+ try {
+ // Use device protected storage context for Direct Boot
+ Context deviceProtectedContext = context;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
+ deviceProtectedContext = context.createDeviceProtectedStorageContext();
+ }
+
+ // Minimal work here - just log that we're ready
+ // Full recovery will happen on BOOT_COMPLETED when storage is available
+ Log.i(TAG, "Locked boot completed - ready for full recovery on unlock");
+
+ } catch (Exception e) {
+ Log.e(TAG, "Error during locked boot completion", e);
+ }
+ }
+
+ /**
+ * Handle device boot completion (after user unlock)
+ *
+ * @param context Application context
+ */
+ private void handleBootCompleted(Context context) {
+ Log.i(TAG, "Device boot completed - restoring notifications");
+
+ try {
+ // Initialize storage to load saved notifications
+ DailyNotificationStorage storage = new DailyNotificationStorage(context);
+
+ // Get all saved notifications
+ java.util.List notifications = storage.getAllNotifications();
+
+ if (notifications.isEmpty()) {
+ Log.i(TAG, "No notifications to recover");
+ return;
+ }
+
+ Log.i(TAG, "Found " + notifications.size() + " notifications to recover");
+
+ // Initialize scheduler for rescheduling
+ android.app.AlarmManager alarmManager = (android.app.AlarmManager)
+ context.getSystemService(android.content.Context.ALARM_SERVICE);
+ DailyNotificationScheduler scheduler = new DailyNotificationScheduler(context, alarmManager);
+
+ // Reschedule each notification
+ int recoveredCount = 0;
+ for (NotificationContent notification : notifications) {
+ try {
+ // Only reschedule future notifications
+ if (notification.getScheduledTime() > System.currentTimeMillis()) {
+ boolean scheduled = scheduler.scheduleNotification(notification);
+ if (scheduled) {
+ recoveredCount++;
+ Log.d(TAG, "Recovered notification: " + notification.getId());
+ } else {
+ Log.w(TAG, "Failed to recover notification: " + notification.getId());
+ }
+ } else {
+ Log.d(TAG, "Skipping past notification: " + notification.getId());
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error recovering notification: " + notification.getId(), e);
+ }
+ }
+
+ Log.i(TAG, "Notification recovery completed: " + recoveredCount + "/" + notifications.size() + " recovered");
+
+ } catch (Exception e) {
+ Log.e(TAG, "Error during boot recovery", e);
+ }
+ }
+
+ /**
+ * Handle package replacement (app update)
+ *
+ * @param context Application context
+ * @param intent Broadcast intent
+ */
+ private void handlePackageReplaced(Context context, Intent intent) {
+ Log.i(TAG, "Package replaced - restoring notifications");
+
+ try {
+ // Use the same recovery logic as boot
+ handleBootCompleted(context);
+
+ } catch (Exception e) {
+ Log.e(TAG, "Error during package replacement recovery", e);
+ }
+ }
+}