From a8eb1956b6d7087d5d3062e408751f2f1eaeaa2c Mon Sep 17 00:00:00 2001 From: Naveen Singh <36371707+naveensingh@users.noreply.github.com> Date: Fri, 23 Jan 2026 12:00:04 +0530 Subject: [PATCH] fix: reschedule scheduled messages when they are cleared (#654) * fix: reschedule scheduled messages when they are cleared * docs: remove comment about overdue messages That will be solved in another PR. * fix: address detekt issues * fix: don't clear scheduled message ahead of time * fix: reschedule scheduled messages on startup This recovers the alarms when app was force-stopped. * fix: typo! * fix: another typo! Refs: https://github.com/FossifyOrg/Messages/issues/641 --- CHANGELOG.md | 3 +++ app/src/main/AndroidManifest.xml | 13 ++++++++++ .../main/kotlin/org/fossify/messages/App.kt | 8 +++++- .../messages/activities/MainActivity.kt | 1 - .../fossify/messages/extensions/Context.kt | 20 ++++++++++++--- .../messages/interfaces/MessagesDao.kt | 10 +++++++- .../receivers/RescheduleAlarmsReceiver.kt | 20 +++++++++++++++ .../receivers/ScheduledMessageReceiver.kt | 25 +++++++++++++++---- 8 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 app/src/main/kotlin/org/fossify/messages/receivers/RescheduleAlarmsReceiver.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 45386e28..6fbe151f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed missing notifications in some cases ([#159]) - Fixed incorrect blocking of MMS messages in some rare cases ([#644]) - Fixed issue with importing alphanumeric blocked numbers ([#282]) +- Fixed issue where scheduled messages were not sent after a reboot or app updates ([#641]) ## [1.7.0] - 2025-12-16 ### Added @@ -206,6 +207,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#209]: https://github.com/FossifyOrg/Messages/issues/209 [#217]: https://github.com/FossifyOrg/Messages/issues/217 [#225]: https://github.com/FossifyOrg/Messages/issues/225 +[#232]: https://github.com/FossifyOrg/Messages/issues/232 [#234]: https://github.com/FossifyOrg/Messages/issues/234 [#243]: https://github.com/FossifyOrg/Messages/issues/243 [#262]: https://github.com/FossifyOrg/Messages/issues/262 @@ -231,6 +233,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#574]: https://github.com/FossifyOrg/Messages/issues/574 [#600]: https://github.com/FossifyOrg/Messages/issues/600 [#610]: https://github.com/FossifyOrg/Messages/issues/610 +[#641]: https://github.com/FossifyOrg/Messages/issues/641 [#644]: https://github.com/FossifyOrg/Messages/issues/644 [#651]: https://github.com/FossifyOrg/Messages/issues/651 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 326db512..2fa856dd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ + @@ -242,6 +243,18 @@ android:name=".receivers.ScheduledMessageReceiver" android:exported="false" /> + + + + + + + + + , newThreadId fun Context.clearExpiredScheduledMessages(threadId: Long, messagesToDelete: List? = null) { val messages = messagesToDelete ?: messagesDB.getScheduledThreadMessages(threadId) - val now = System.currentTimeMillis() + 500L + val cutoff = System.currentTimeMillis() - 1.minutes.inWholeMilliseconds try { - messages.filter { it.isScheduled && it.millis() < now }.forEach { msg -> + messages.filter { it.isScheduled && it.millis() < cutoff }.forEach { msg -> messagesDB.delete(msg.id) } - if (messages.filterNot { it.isScheduled && it.millis() < now }.isEmpty()) { + if (messages.filterNot { it.isScheduled && it.millis() < cutoff }.isEmpty()) { // delete empty temporary thread val conversation = conversationsDB.getConversationWithThreadId(threadId) if (conversation != null && conversation.isScheduled) { @@ -1330,6 +1332,18 @@ fun Context.clearExpiredScheduledMessages(threadId: Long, messagesToDelete: List } } +fun Context.rescheduleAllScheduledMessages() { + val scheduledMessages = try { + messagesDB.getAllScheduledMessages() + } catch (_: Exception) { + return + } + + scheduledMessages.forEach { message -> + runCatching { scheduleMessage(message) } + } +} + fun Context.getDefaultKeyboardHeight(): Int { return resources.getDimensionPixelSize(R.dimen.default_keyboard_height) } diff --git a/app/src/main/kotlin/org/fossify/messages/interfaces/MessagesDao.kt b/app/src/main/kotlin/org/fossify/messages/interfaces/MessagesDao.kt index f97efe3e..f8a37a10 100644 --- a/app/src/main/kotlin/org/fossify/messages/interfaces/MessagesDao.kt +++ b/app/src/main/kotlin/org/fossify/messages/interfaces/MessagesDao.kt @@ -1,6 +1,11 @@ +@file:Suppress("MaxLineLength") package org.fossify.messages.interfaces -import androidx.room.* +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Transaction import org.fossify.messages.models.Message import org.fossify.messages.models.RecycleBinMessage @@ -24,6 +29,9 @@ interface MessagesDao { @Query("SELECT messages.* FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NOT NULL") fun getAllRecycleBinMessages(): List + @Query("SELECT messages.* FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NULL AND is_scheduled = 1") + fun getAllScheduledMessages(): List + @Query("SELECT messages.* FROM messages LEFT OUTER JOIN recycle_bin_messages ON messages.id = recycle_bin_messages.id WHERE recycle_bin_messages.id IS NOT NULL AND recycle_bin_messages.deleted_ts < :timestamp") fun getOldRecycleBinMessages(timestamp: Long): List diff --git a/app/src/main/kotlin/org/fossify/messages/receivers/RescheduleAlarmsReceiver.kt b/app/src/main/kotlin/org/fossify/messages/receivers/RescheduleAlarmsReceiver.kt new file mode 100644 index 00000000..33040518 --- /dev/null +++ b/app/src/main/kotlin/org/fossify/messages/receivers/RescheduleAlarmsReceiver.kt @@ -0,0 +1,20 @@ +package org.fossify.messages.receivers + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import org.fossify.commons.helpers.ensureBackgroundThread +import org.fossify.messages.extensions.rescheduleAllScheduledMessages + +/** + * Reschedules alarms after boot/package updates. + */ +class RescheduleAlarmsReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val pendingResult = goAsync() + ensureBackgroundThread { + context.rescheduleAllScheduledMessages() + pendingResult.finish() + } + } +} diff --git a/app/src/main/kotlin/org/fossify/messages/receivers/ScheduledMessageReceiver.kt b/app/src/main/kotlin/org/fossify/messages/receivers/ScheduledMessageReceiver.kt index 8764c4fc..5aab2d2d 100644 --- a/app/src/main/kotlin/org/fossify/messages/receivers/ScheduledMessageReceiver.kt +++ b/app/src/main/kotlin/org/fossify/messages/receivers/ScheduledMessageReceiver.kt @@ -17,17 +17,30 @@ import org.fossify.messages.helpers.THREAD_ID import org.fossify.messages.helpers.refreshConversations import org.fossify.messages.helpers.refreshMessages import org.fossify.messages.messaging.sendMessageCompat +import kotlin.time.Duration.Companion.minutes class ScheduledMessageReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager - val wakelock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "simple.messenger:scheduled.message.receiver") - wakelock.acquire(3000) - + val wakelock = powerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, + "simple.messenger:scheduled.message.receiver" + ) + wakelock.acquire(1.minutes.inWholeMilliseconds) + val pendingResult = goAsync() ensureBackgroundThread { - handleIntent(context, intent) + try { + handleIntent(context, intent) + } finally { + try { + if (wakelock.isHeld) wakelock.release() + } catch (_: Exception) { + } + + pendingResult.finish() + } } } @@ -57,7 +70,9 @@ class ScheduledMessageReceiver : BroadcastReceiver() { } catch (e: Exception) { context.showErrorToast(e) } catch (e: Error) { - context.showErrorToast(e.localizedMessage ?: context.getString(org.fossify.commons.R.string.unknown_error_occurred)) + context.showErrorToast( + e.localizedMessage ?: context.getString(org.fossify.commons.R.string.unknown_error_occurred) + ) } } }