Improve SMS delivery report handling

On some phones, SMS message is marked delivered even though it wasn't really delivered, this should fix such issues. Based on AOSP and Signal app
This commit is contained in:
Naveen 2023-01-05 23:51:29 +05:30
parent f24001ea3d
commit 08ee7ac700
3 changed files with 69 additions and 60 deletions

View File

@ -3,9 +3,11 @@ package com.simplemobiletools.smsmessenger.messaging
import android.app.Activity import android.app.Activity
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.Intent
import android.net.Uri import android.net.Uri
import android.provider.Telephony.Sms import android.provider.Telephony.Sms
import android.telephony.SmsManager import android.telephony.SmsManager
import android.telephony.SmsMessage
import android.widget.Toast import android.widget.Toast
import com.klinker.android.send_message.Message import com.klinker.android.send_message.Message
import com.klinker.android.send_message.Settings import com.klinker.android.send_message.Settings
@ -88,6 +90,12 @@ class MessagingUtils(val context: Context) {
} }
} }
fun getSmsMessageFromDeliveryReport(intent: Intent): SmsMessage? {
val pdu = intent.getByteArrayExtra("pdu")
val format = intent.getStringExtra("format")
return SmsMessage.createFromPdu(pdu, format)
}
@Deprecated("TODO: Move/rewrite MMS code into the app.") @Deprecated("TODO: Move/rewrite MMS code into the app.")
fun sendMmsMessage(text: String, addresses: List<String>, attachments: List<Attachment>, settings: Settings) { fun sendMmsMessage(text: String, addresses: List<String>, attachments: List<Attachment>, settings: Settings) {
val transaction = Transaction(context, settings) val transaction = Transaction(context, settings)

View File

@ -5,6 +5,7 @@ import android.app.PendingIntent
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.telephony.PhoneNumberUtils import android.telephony.PhoneNumberUtils
import com.simplemobiletools.commons.helpers.isSPlus
import com.simplemobiletools.smsmessenger.messaging.SmsException.Companion.EMPTY_DESTINATION_ADDRESS import com.simplemobiletools.smsmessenger.messaging.SmsException.Companion.EMPTY_DESTINATION_ADDRESS
import com.simplemobiletools.smsmessenger.messaging.SmsException.Companion.ERROR_SENDING_MESSAGE import com.simplemobiletools.smsmessenger.messaging.SmsException.Companion.ERROR_SENDING_MESSAGE
import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver
@ -56,7 +57,10 @@ class SmsSender(val app: Application) {
val deliveryIntents = ArrayList<PendingIntent?>(messageCount) val deliveryIntents = ArrayList<PendingIntent?>(messageCount)
val sentIntents = ArrayList<PendingIntent>(messageCount) val sentIntents = ArrayList<PendingIntent>(messageCount)
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE var flags = PendingIntent.FLAG_UPDATE_CURRENT
if (isSPlus()) {
flags = flags or PendingIntent.FLAG_MUTABLE
}
for (i in 0 until messageCount) { for (i in 0 until messageCount) {
// Make pending intents different for each message part // Make pending intents different for each message part

View File

@ -1,99 +1,96 @@
package com.simplemobiletools.smsmessenger.receivers package com.simplemobiletools.smsmessenger.receivers
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.provider.Telephony.Sms import android.provider.Telephony.Sms
import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.extensions.messagesDB import com.simplemobiletools.smsmessenger.extensions.messagesDB
import com.simplemobiletools.smsmessenger.extensions.messagingUtils
import com.simplemobiletools.smsmessenger.helpers.refreshMessages import com.simplemobiletools.smsmessenger.helpers.refreshMessages
import com.simplemobiletools.smsmessenger.messaging.SendStatusReceiver import com.simplemobiletools.smsmessenger.messaging.SendStatusReceiver
/** Handles updating databases and states when a sent SMS message is delivered. */ /** Handles updating databases and states when a sent SMS message is delivered. */
class SmsStatusDeliveredReceiver : SendStatusReceiver() { class SmsStatusDeliveredReceiver : SendStatusReceiver() {
private var status: Int = Sms.Sent.STATUS_NONE
override fun updateAndroidDatabase(context: Context, intent: Intent, receiverResultCode: Int) { override fun updateAndroidDatabase(context: Context, intent: Intent, receiverResultCode: Int) {
val uri: Uri? = intent.data val messageUri: Uri? = intent.data
val resultCode = resultCode val smsMessage = context.messagingUtils.getSmsMessageFromDeliveryReport(intent) ?: return
try { try {
when (resultCode) { val format = intent.getStringExtra("format")
Activity.RESULT_OK -> { status = smsMessage.status
if (uri != null) { // Simple matching up CDMA status with GSM status.
val values = ContentValues().apply { if ("3gpp2" == format) {
put(Sms.Sent.STATUS, Sms.Sent.STATUS_COMPLETE) val errorClass = status shr 24 and 0x03
put(Sms.Sent.DATE_SENT, System.currentTimeMillis()) val statusCode = status shr 16 and 0x3f
put(Sms.Sent.READ, true) status = when (errorClass) {
} 0 -> {
context.contentResolver.update(uri, values, null, null) if (statusCode == 0x02 /*STATUS_DELIVERED*/) {
Sms.STATUS_COMPLETE
} else { } else {
updateLatestSmsStatus(context, status = Sms.Sent.STATUS_COMPLETE, read = true, date = System.currentTimeMillis()) Sms.STATUS_PENDING
} }
} }
Activity.RESULT_CANCELED -> { 2 -> {
if (uri != null) { // TODO: Need to check whether SC still trying to deliver the SMS to destination and will send the report again?
val values = ContentValues().apply { Sms.STATUS_PENDING
put(Sms.Sent.STATUS, Sms.Sent.STATUS_FAILED) }
put(Sms.Sent.DATE_SENT, System.currentTimeMillis()) 3 -> {
put(Sms.Sent.READ, true) Sms.STATUS_FAILED
put(Sms.Sent.ERROR_CODE, resultCode) }
} else -> {
context.contentResolver.update(uri, values, null, null) Sms.STATUS_PENDING
} else { }
updateLatestSmsStatus(context, status = Sms.Sent.STATUS_FAILED, read = true, errorCode = resultCode) }
} }
} } catch (e: NullPointerException) {
} // Sometimes, SmsMessage.mWrappedSmsMessage is null causing NPE when we access
} catch (e: Exception) { // the methods on it although the SmsMessage itself is not null.
e.printStackTrace() return
} }
}
updateSmsStatusAndDateSent(context, messageUri, System.currentTimeMillis())
@SuppressLint("Range") }
private fun updateLatestSmsStatus(context: Context, status: Int, read: Boolean, date: Long = -1L, errorCode: Int = -1) {
val query = context.contentResolver.query(Sms.Sent.CONTENT_URI, null, null, null, "date desc") private fun updateSmsStatusAndDateSent(context: Context, messageUri: Uri?, timeSentInMillis: Long = -1L) {
val resolver = context.contentResolver
// mark message as delivered in database
if (query!!.moveToFirst()) {
val id = query.getString(query.getColumnIndex(Sms.Sent._ID))
val values = ContentValues().apply { val values = ContentValues().apply {
if (status != Sms.Sent.STATUS_NONE) {
put(Sms.Sent.STATUS, status) put(Sms.Sent.STATUS, status)
put(Sms.Sent.READ, read)
if (date != -1L) {
put(Sms.Sent.DATE_SENT, date)
}
if (errorCode != -1) {
put(Sms.Sent.ERROR_CODE, errorCode)
} }
put(Sms.Sent.DATE_SENT, timeSentInMillis)
} }
context.contentResolver.update(Sms.Sent.CONTENT_URI, values, "_id=$id", null) if (messageUri != null) {
resolver.update(messageUri, values, null, null)
} else {
// mark latest sms as delivered, need to check if this is still necessary (or reliable)
val cursor = resolver.query(Sms.Sent.CONTENT_URI, null, null, null, "date desc")
cursor?.use {
if (cursor.moveToFirst()) {
@SuppressLint("Range")
val id = cursor.getString(cursor.getColumnIndex(Sms.Sent._ID))
val selection = "${Sms._ID} = ?"
val selectionArgs = arrayOf(id.toString())
resolver.update(Sms.Sent.CONTENT_URI, values, selection, selectionArgs)
}
}
} }
query.close()
} }
override fun updateAppDatabase(context: Context, intent: Intent, receiverResultCode: Int) { override fun updateAppDatabase(context: Context, intent: Intent, receiverResultCode: Int) {
val uri = intent.data val messageUri: Uri? = intent.data
if (uri != null) { if (messageUri != null) {
val messageId = uri.lastPathSegment?.toLong() ?: 0L val messageId = messageUri.lastPathSegment?.toLong() ?: 0L
ensureBackgroundThread {
val status = Sms.Sent.STATUS_COMPLETE
val updated = context.messagesDB.updateStatus(messageId, status)
if (updated == 0) {
Handler(Looper.getMainLooper()).postDelayed({
ensureBackgroundThread { ensureBackgroundThread {
if (status != Sms.Sent.STATUS_NONE) {
context.messagesDB.updateStatus(messageId, status) context.messagesDB.updateStatus(messageId, status)
} }
}, 2000)
}
refreshMessages() refreshMessages()
} }
} }