fix(android): restore user title/body after reboot so notification doesn't show fallback text
After device restart, PendingIntent extras (title, body, is_static_reminder) can be missing when the alarm fires, so the worker took the Room/JIT path and showed fallback text instead of the user's message. - DailyNotificationReceiver: when intent has notification_id but missing title/body, load NotificationContentEntity from Room and pass title/body into Worker input with is_static_reminder=true. - ReactivationManager: add getTitleBodyForSchedule(); use persisted title/body in rescheduleAlarm and rescheduleAlarmForBoot (and inner boot helper) instead of hardcoded "Daily Notification" / "Your daily update is ready". - BootReceiver: use ReactivationManager.getTitleBodyForSchedule() when building UserNotificationConfig for notify schedules after boot. - DailyNotificationWorker: when content from Room has both title and body, skip performJITFreshnessCheck so user text is not overwritten by fetcher placeholder. Ref: plugin-feedback-android-post-reboot-fallback-text (crowd-funder-for-time-pwa)
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
*
|
||||
@@ -110,6 +112,7 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
|
||||
|
||||
// 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");
|
||||
@@ -120,6 +123,28 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
|
||||
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")
|
||||
@@ -196,6 +221,15 @@ public class DailyNotificationReceiver extends BroadcastReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
|
||||
@@ -177,8 +177,15 @@ public class DailyNotificationWorker extends Worker {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
// JIT Freshness Re-check (Soft TTL) - skip for static reminders
|
||||
// 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
|
||||
|
||||
@@ -42,6 +42,26 @@ class ReactivationManager(private val context: Context) {
|
||||
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<String, String>? {
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user