diff --git a/android/src/main/java/com/timesafari/dailynotification/BootReceiver.kt b/android/src/main/java/com/timesafari/dailynotification/BootReceiver.kt index bdba635..2aadae7 100644 --- a/android/src/main/java/com/timesafari/dailynotification/BootReceiver.kt +++ b/android/src/main/java/com/timesafari/dailynotification/BootReceiver.kt @@ -76,11 +76,13 @@ class BootReceiver : BroadcastReceiver() { // Reschedule AlarmManager notification val nextRunTime = calculateNextRunTime(schedule) if (nextRunTime > System.currentTimeMillis()) { + val (title, body) = ReactivationManager.getTitleBodyForSchedule(db, schedule) + ?: Pair("Daily Notification", "Your daily update is ready") val config = UserNotificationConfig( enabled = schedule.enabled, schedule = schedule.cron ?: schedule.clockTime ?: "0 9 * * *", - title = "Daily Notification", - body = "Your daily update is ready", + title = title, + body = body, sound = true, vibration = true, priority = "normal" diff --git a/android/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java b/android/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java index af979fd..844e6e4 100644 --- a/android/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java +++ b/android/src/main/java/com/timesafari/dailynotification/DailyNotificationReceiver.java @@ -25,6 +25,8 @@ import androidx.work.ExistingWorkPolicy; import androidx.work.OneTimeWorkRequest; import androidx.work.WorkManager; +import com.timesafari.dailynotification.entities.NotificationContentEntity; + /** * Broadcast receiver for daily notification alarms * @@ -107,9 +109,10 @@ public class DailyNotificationReceiver extends BroadcastReceiver { // Create unique work name based on notification ID to prevent duplicates // WorkManager will automatically skip if work with this name already exists String workName = "display_" + notificationId; - + // Extract static reminder extras from intent if present // Static reminders have title/body in Intent extras, not in storage + // After reboot, Intent extras may be missing; restore from DB when possible boolean isStaticReminder = intent.getBooleanExtra("is_static_reminder", false); String title = intent.getStringExtra("title"); String body = intent.getStringExtra("body"); @@ -119,12 +122,34 @@ public class DailyNotificationReceiver extends BroadcastReceiver { if (priority == null) { priority = "normal"; } - + + // Post-reboot fallback: if we have notification_id but missing title/body, load from DB + // so the worker can display user-set text instead of fallback (see plugin-feedback-android-post-reboot-fallback-text) + if ((title == null || title.isEmpty() || body == null || body.isEmpty()) && notificationId != null) { + try { + com.timesafari.dailynotification.DailyNotificationDatabase db = + com.timesafari.dailynotification.DailyNotificationDatabase.getInstance(context); + NotificationContentEntity entity = db.notificationContentDao().getNotificationById(notificationId); + if (entity != null && entity.title != null && !entity.title.isEmpty() + && entity.body != null && !entity.body.isEmpty()) { + title = entity.title; + body = entity.body; + isStaticReminder = true; + sound = entity.soundEnabled; + vibration = entity.vibrationEnabled; + priority = mapPriorityFromInt(entity.priority); + Log.d(TAG, "DN|WORK_ENQUEUE static_reminder_from_db id=" + notificationId); + } + } catch (Exception e) { + Log.w(TAG, "DN|WORK_ENQUEUE db_fallback_failed id=" + notificationId + " err=" + e.getMessage()); + } + } + Data.Builder dataBuilder = new Data.Builder() .putString("notification_id", notificationId) .putString("action", "display") .putBoolean("is_static_reminder", isStaticReminder); - + // Add static reminder data if present if (isStaticReminder && title != null && body != null) { dataBuilder.putString("title", title) @@ -134,7 +159,7 @@ public class DailyNotificationReceiver extends BroadcastReceiver { .putString("priority", priority); Log.d(TAG, "DN|WORK_ENQUEUE static_reminder id=" + notificationId); } - + Data inputData = dataBuilder.build(); OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(DailyNotificationWorker.class) @@ -195,6 +220,15 @@ public class DailyNotificationReceiver extends BroadcastReceiver { Log.e(TAG, "DN|WORK_ENQUEUE_ERR dismiss=" + notificationId + " err=" + e.getMessage(), e); } } + + /** + * Map Room priority int to string for Worker input. + */ + private static String mapPriorityFromInt(int p) { + if (p >= 2) return "high"; + if (p <= -1) return "low"; + return "normal"; + } /** * Handle notification intent diff --git a/android/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java b/android/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java index ff78256..fa08eca 100644 --- a/android/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java +++ b/android/src/main/java/com/timesafari/dailynotification/DailyNotificationWorker.java @@ -177,8 +177,15 @@ public class DailyNotificationWorker extends Worker { return Result.success(); } - // JIT Freshness Re-check (Soft TTL) - skip for static reminders - content = performJITFreshnessCheck(content); + // JIT Freshness Re-check (Soft TTL) - skip when content has title/body from Room + // (e.g. post-reboot static reminder restored from DB so we don't overwrite with fetcher fallback) + boolean hasTitleBody = content.getTitle() != null && !content.getTitle().isEmpty() + && content.getBody() != null && !content.getBody().isEmpty(); + if (!hasTitleBody) { + content = performJITFreshnessCheck(content); + } else { + Log.d(TAG, "DN|DISPLAY_USE_ROOM_CONTENT id=" + notificationId + " (skip JIT)"); + } } // Display the notification diff --git a/android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt b/android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt index 1d30ff2..8941107 100644 --- a/android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt +++ b/android/src/main/java/com/timesafari/dailynotification/ReactivationManager.kt @@ -41,6 +41,26 @@ class ReactivationManager(private val context: Context) { companion object { private const val TAG = "DNP-REACTIVATION" private const val RECOVERY_TIMEOUT_SECONDS = 2L + + /** + * Load persisted title/body for a schedule from NotificationContentEntity (post-reboot recovery). + * Tries schedule.id then "daily_${schedule.id}" to match NotifyReceiver/ScheduleHelper id convention. + * Internal so BootReceiver can use when rescheduling after boot. + */ + internal fun getTitleBodyForSchedule(db: DailyNotificationDatabase, schedule: Schedule): Pair? { + val entity = try { + db.notificationContentDao().getNotificationById(schedule.id) + } catch (_: Exception) { + null + } ?: try { + db.notificationContentDao().getNotificationById("daily_${schedule.id}") + } catch (_: Exception) { + null + } ?: return null + val t = entity.title?.takeIf { it.isNotBlank() } ?: return null + val b = entity.body?.takeIf { it.isNotBlank() } ?: return null + return Pair(t, b) + } /** * Run boot-time recovery @@ -275,11 +295,13 @@ class ReactivationManager(private val context: Context) { db: DailyNotificationDatabase ) { try { + val (title, body) = getTitleBodyForSchedule(db, schedule) + ?: Pair("Daily Notification", "Your daily update is ready") val config = UserNotificationConfig( enabled = schedule.enabled, schedule = schedule.cron ?: schedule.clockTime ?: "0 9 * * *", - title = "Daily Notification", - body = "Your daily update is ready", + title = title, + body = body, sound = true, vibration = true, priority = "normal" @@ -816,13 +838,13 @@ class ReactivationManager(private val context: Context) { db: DailyNotificationDatabase ) { try { - // Use existing BootReceiver logic for calculating next run time - // For now, use schedule.nextRunAt directly + val (title, body) = ReactivationManager.getTitleBodyForSchedule(db, schedule) + ?: Pair("Daily Notification", "Your daily update is ready") val config = UserNotificationConfig( enabled = schedule.enabled, schedule = schedule.cron ?: schedule.clockTime ?: "0 9 * * *", - title = "Daily Notification", - body = "Your daily update is ready", + title = title, + body = body, sound = true, vibration = true, priority = "normal" @@ -1045,11 +1067,13 @@ class ReactivationManager(private val context: Context) { db: DailyNotificationDatabase ) { try { + val (title, body) = ReactivationManager.getTitleBodyForSchedule(db, schedule) + ?: Pair("Daily Notification", "Your daily update is ready") val config = UserNotificationConfig( enabled = schedule.enabled, schedule = schedule.cron ?: schedule.clockTime ?: "0 9 * * *", - title = "Daily Notification", - body = "Your daily update is ready", + title = title, + body = body, sound = true, vibration = true, priority = "normal"