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
This commit is contained in:
parent
dd4ff67a72
commit
a8eb1956b6
8 changed files with 89 additions and 11 deletions
|
|
@ -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 missing notifications in some cases ([#159])
|
||||||
- Fixed incorrect blocking of MMS messages in some rare cases ([#644])
|
- Fixed incorrect blocking of MMS messages in some rare cases ([#644])
|
||||||
- Fixed issue with importing alphanumeric blocked numbers ([#282])
|
- 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
|
## [1.7.0] - 2025-12-16
|
||||||
### Added
|
### 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
|
[#209]: https://github.com/FossifyOrg/Messages/issues/209
|
||||||
[#217]: https://github.com/FossifyOrg/Messages/issues/217
|
[#217]: https://github.com/FossifyOrg/Messages/issues/217
|
||||||
[#225]: https://github.com/FossifyOrg/Messages/issues/225
|
[#225]: https://github.com/FossifyOrg/Messages/issues/225
|
||||||
|
[#232]: https://github.com/FossifyOrg/Messages/issues/232
|
||||||
[#234]: https://github.com/FossifyOrg/Messages/issues/234
|
[#234]: https://github.com/FossifyOrg/Messages/issues/234
|
||||||
[#243]: https://github.com/FossifyOrg/Messages/issues/243
|
[#243]: https://github.com/FossifyOrg/Messages/issues/243
|
||||||
[#262]: https://github.com/FossifyOrg/Messages/issues/262
|
[#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
|
[#574]: https://github.com/FossifyOrg/Messages/issues/574
|
||||||
[#600]: https://github.com/FossifyOrg/Messages/issues/600
|
[#600]: https://github.com/FossifyOrg/Messages/issues/600
|
||||||
[#610]: https://github.com/FossifyOrg/Messages/issues/610
|
[#610]: https://github.com/FossifyOrg/Messages/issues/610
|
||||||
|
[#641]: https://github.com/FossifyOrg/Messages/issues/641
|
||||||
[#644]: https://github.com/FossifyOrg/Messages/issues/644
|
[#644]: https://github.com/FossifyOrg/Messages/issues/644
|
||||||
[#651]: https://github.com/FossifyOrg/Messages/issues/651
|
[#651]: https://github.com/FossifyOrg/Messages/issues/651
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
<uses-permission android:name="android.provider.Telephony.SMS_RECEIVED" />
|
<uses-permission android:name="android.provider.Telephony.SMS_RECEIVED" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||||
|
|
@ -242,6 +243,18 @@
|
||||||
android:name=".receivers.ScheduledMessageReceiver"
|
android:name=".receivers.ScheduledMessageReceiver"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".receivers.RescheduleAlarmsReceiver"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||||
|
<action android:name="android.intent.action.TIME_SET" />
|
||||||
|
<action android:name="android.intent.action.TIMEZONE_CHANGED" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.provider"
|
android:authorities="${applicationId}.provider"
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import android.provider.ContactsContract
|
||||||
import org.fossify.commons.FossifyApp
|
import org.fossify.commons.FossifyApp
|
||||||
import org.fossify.commons.extensions.hasPermission
|
import org.fossify.commons.extensions.hasPermission
|
||||||
import org.fossify.commons.helpers.PERMISSION_READ_CONTACTS
|
import org.fossify.commons.helpers.PERMISSION_READ_CONTACTS
|
||||||
|
import org.fossify.commons.helpers.ensureBackgroundThread
|
||||||
|
import org.fossify.messages.extensions.rescheduleAllScheduledMessages
|
||||||
import org.fossify.messages.helpers.MessagingCache
|
import org.fossify.messages.helpers.MessagingCache
|
||||||
|
|
||||||
class App : FossifyApp() {
|
class App : FossifyApp() {
|
||||||
|
|
@ -23,10 +25,14 @@ class App : FossifyApp() {
|
||||||
).forEach {
|
).forEach {
|
||||||
try {
|
try {
|
||||||
contentResolver.registerContentObserver(it, true, contactsObserver)
|
contentResolver.registerContentObserver(it, true, contactsObserver)
|
||||||
} catch (_: Exception){
|
} catch (_: Exception) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureBackgroundThread {
|
||||||
|
rescheduleAllScheduledMessages()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val contactsObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
|
private val contactsObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import android.os.Bundle
|
||||||
import android.provider.Telephony
|
import android.provider.Telephony
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
||||||
import org.fossify.commons.dialogs.PermissionRequiredDialog
|
import org.fossify.commons.dialogs.PermissionRequiredDialog
|
||||||
import org.fossify.commons.extensions.adjustAlpha
|
import org.fossify.commons.extensions.adjustAlpha
|
||||||
import org.fossify.commons.extensions.appLaunched
|
import org.fossify.commons.extensions.appLaunched
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ import org.fossify.messages.interfaces.MessagesDao
|
||||||
import org.fossify.messages.messaging.MessagingUtils
|
import org.fossify.messages.messaging.MessagingUtils
|
||||||
import org.fossify.messages.messaging.MessagingUtils.Companion.ADDRESS_SEPARATOR
|
import org.fossify.messages.messaging.MessagingUtils.Companion.ADDRESS_SEPARATOR
|
||||||
import org.fossify.messages.messaging.SmsSender
|
import org.fossify.messages.messaging.SmsSender
|
||||||
|
import org.fossify.messages.messaging.scheduleMessage
|
||||||
import org.fossify.messages.models.Attachment
|
import org.fossify.messages.models.Attachment
|
||||||
import org.fossify.messages.models.Conversation
|
import org.fossify.messages.models.Conversation
|
||||||
import org.fossify.messages.models.Draft
|
import org.fossify.messages.models.Draft
|
||||||
|
|
@ -77,6 +78,7 @@ import org.fossify.messages.models.NamePhoto
|
||||||
import org.fossify.messages.models.RecycleBinMessage
|
import org.fossify.messages.models.RecycleBinMessage
|
||||||
import org.xmlpull.v1.XmlPullParserException
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
val Context.config: Config
|
val Context.config: Config
|
||||||
get() = Config.newInstance(applicationContext)
|
get() = Config.newInstance(applicationContext)
|
||||||
|
|
@ -1311,13 +1313,13 @@ fun Context.updateScheduledMessagesThreadId(messages: List<Message>, newThreadId
|
||||||
|
|
||||||
fun Context.clearExpiredScheduledMessages(threadId: Long, messagesToDelete: List<Message>? = null) {
|
fun Context.clearExpiredScheduledMessages(threadId: Long, messagesToDelete: List<Message>? = null) {
|
||||||
val messages = messagesToDelete ?: messagesDB.getScheduledThreadMessages(threadId)
|
val messages = messagesToDelete ?: messagesDB.getScheduledThreadMessages(threadId)
|
||||||
val now = System.currentTimeMillis() + 500L
|
val cutoff = System.currentTimeMillis() - 1.minutes.inWholeMilliseconds
|
||||||
|
|
||||||
try {
|
try {
|
||||||
messages.filter { it.isScheduled && it.millis() < now }.forEach { msg ->
|
messages.filter { it.isScheduled && it.millis() < cutoff }.forEach { msg ->
|
||||||
messagesDB.delete(msg.id)
|
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
|
// delete empty temporary thread
|
||||||
val conversation = conversationsDB.getConversationWithThreadId(threadId)
|
val conversation = conversationsDB.getConversationWithThreadId(threadId)
|
||||||
if (conversation != null && conversation.isScheduled) {
|
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 {
|
fun Context.getDefaultKeyboardHeight(): Int {
|
||||||
return resources.getDimensionPixelSize(R.dimen.default_keyboard_height)
|
return resources.getDimensionPixelSize(R.dimen.default_keyboard_height)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
|
@file:Suppress("MaxLineLength")
|
||||||
package org.fossify.messages.interfaces
|
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.Message
|
||||||
import org.fossify.messages.models.RecycleBinMessage
|
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")
|
@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<Message>
|
fun getAllRecycleBinMessages(): List<Message>
|
||||||
|
|
||||||
|
@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<Message>
|
||||||
|
|
||||||
@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")
|
@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<Message>
|
fun getOldRecycleBinMessages(timestamp: Long): List<Message>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,17 +17,30 @@ import org.fossify.messages.helpers.THREAD_ID
|
||||||
import org.fossify.messages.helpers.refreshConversations
|
import org.fossify.messages.helpers.refreshConversations
|
||||||
import org.fossify.messages.helpers.refreshMessages
|
import org.fossify.messages.helpers.refreshMessages
|
||||||
import org.fossify.messages.messaging.sendMessageCompat
|
import org.fossify.messages.messaging.sendMessageCompat
|
||||||
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
class ScheduledMessageReceiver : BroadcastReceiver() {
|
class ScheduledMessageReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
val wakelock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "simple.messenger:scheduled.message.receiver")
|
val wakelock = powerManager.newWakeLock(
|
||||||
wakelock.acquire(3000)
|
PowerManager.PARTIAL_WAKE_LOCK,
|
||||||
|
"simple.messenger:scheduled.message.receiver"
|
||||||
|
)
|
||||||
|
wakelock.acquire(1.minutes.inWholeMilliseconds)
|
||||||
|
|
||||||
|
val pendingResult = goAsync()
|
||||||
ensureBackgroundThread {
|
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) {
|
} catch (e: Exception) {
|
||||||
context.showErrorToast(e)
|
context.showErrorToast(e)
|
||||||
} catch (e: Error) {
|
} 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)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue