Handle sending scheduled messages

This commit is contained in:
Naveen
2022-09-27 16:08:45 +05:30
parent 16ea540d48
commit 2ff0880cb5
14 changed files with 306 additions and 75 deletions

View File

@@ -11,6 +11,7 @@
<uses-permission android:name="android.permission.RECEIVE_MMS" /> <uses-permission android:name="android.permission.RECEIVE_MMS" />
<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.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" />
@@ -212,6 +213,10 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver
android:name=".receivers.ScheduledMessageReceiver"
android:exported="false" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"

View File

@@ -243,7 +243,7 @@ class MainActivity : SimpleActivity() {
if (config.appRunCount == 1) { if (config.appRunCount == 1) {
conversations.map { it.threadId }.forEach { threadId -> conversations.map { it.threadId }.forEach { threadId ->
val messages = getMessages(threadId, false) val messages = getMessages(threadId, getImageResolutions = false, includeScheduledMessages = false)
messages.chunked(30).forEach { currentMessages -> messages.chunked(30).forEach { currentMessages ->
messagesDB.insertMessages(*currentMessages.toTypedArray()) messagesDB.insertMessages(*currentMessages.toTypedArray())
} }

View File

@@ -13,6 +13,8 @@ import android.os.Bundle
import android.provider.ContactsContract import android.provider.ContactsContract
import android.provider.MediaStore import android.provider.MediaStore
import android.provider.Telephony import android.provider.Telephony
import android.provider.Telephony.Sms.MESSAGE_TYPE_QUEUED
import android.provider.Telephony.Sms.STATUS_NONE
import android.telephony.SmsManager import android.telephony.SmsManager
import android.telephony.SmsMessage import android.telephony.SmsMessage
import android.telephony.SubscriptionInfo import android.telephony.SubscriptionInfo
@@ -92,6 +94,7 @@ class ThreadActivity : SimpleActivity() {
private var oldestMessageDate = -1 private var oldestMessageDate = -1
private var isScheduledMessage: Boolean = false private var isScheduledMessage: Boolean = false
private var scheduledMessage: Message? = null
private lateinit var scheduledDateTime: DateTime private lateinit var scheduledDateTime: DateTime
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -260,8 +263,8 @@ class ThreadActivity : SimpleActivity() {
val cachedMessagesCode = messages.clone().hashCode() val cachedMessagesCode = messages.clone().hashCode()
messages = getMessages(threadId, true) messages = getMessages(threadId, true)
val hasParticipantWithoutName = participants.any { val hasParticipantWithoutName = participants.any { contact ->
it.phoneNumbers.map { it.normalizedNumber }.contains(it.name) contact.phoneNumbers.map { it.normalizedNumber }.contains(contact.name)
} }
try { try {
@@ -327,10 +330,8 @@ class ThreadActivity : SimpleActivity() {
val currAdapter = thread_messages_list.adapter val currAdapter = thread_messages_list.adapter
if (currAdapter == null) { if (currAdapter == null) {
ThreadAdapter(this, threadItems, thread_messages_list) { ThreadAdapter(this, threadItems, thread_messages_list) { any ->
(it as? ThreadError)?.apply { handleItemClick(any)
thread_type_message.setText(it.messageText)
}
}.apply { }.apply {
thread_messages_list.adapter = this thread_messages_list.adapter = this
} }
@@ -373,6 +374,13 @@ class ThreadActivity : SimpleActivity() {
} }
} }
private fun handleItemClick(any: Any) {
when {
any is Message && any.isScheduled -> showScheduledMessageInfo(any)
any is ThreadError -> thread_type_message.setText(any.messageText)
}
}
private fun fetchNextMessages() { private fun fetchNextMessages() {
if (messages.isEmpty() || allMessagesFetched || loadingOlderMessages) { if (messages.isEmpty() || allMessagesFetched || loadingOlderMessages) {
return return
@@ -590,8 +598,7 @@ class ThreadActivity : SimpleActivity() {
val defaultSmsSubscriptionId = SmsManager.getDefaultSmsSubscriptionId() val defaultSmsSubscriptionId = SmsManager.getDefaultSmsSubscriptionId()
val systemPreferredSimIdx = if (defaultSmsSubscriptionId >= 0) { val systemPreferredSimIdx = if (defaultSmsSubscriptionId >= 0) {
val defaultSmsSIM = subscriptionManagerCompat().getActiveSubscriptionInfo(defaultSmsSubscriptionId) availableSIMs.indexOfFirstOrNull { it.subscriptionId == defaultSmsSubscriptionId }
availableSIMs.indexOfFirstOrNull { it.subscriptionId == defaultSmsSIM.subscriptionId }
} else { } else {
null null
} }
@@ -600,13 +607,7 @@ class ThreadActivity : SimpleActivity() {
} }
private fun blockNumber() { private fun blockNumber() {
val numbers = ArrayList<String>() val numbers = participants.getAddresses()
participants.forEach {
it.phoneNumbers.forEach {
numbers.add(it.normalizedNumber)
}
}
val numbersString = TextUtils.join(", ", numbers) val numbersString = TextUtils.join(", ", numbers)
val question = String.format(resources.getString(R.string.block_confirmation), numbersString) val question = String.format(resources.getString(R.string.block_confirmation), numbersString)
@@ -939,21 +940,44 @@ class ThreadActivity : SimpleActivity() {
text = removeDiacriticsIfNeeded(text) text = removeDiacriticsIfNeeded(text)
val addresses = participants val subscriptionId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId ?: SmsManager.getDefaultSmsSubscriptionId()
.flatMap { it.phoneNumbers }
.map { it.normalizedNumber }
val currentSubscriptionId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId if (isScheduledMessage) {
val attachments = attachmentSelections.values.map { it.uri } sendScheduledMessage(text, subscriptionId)
} else {
sendNormalMessage(text, subscriptionId)
}
}
private fun sendScheduledMessage(text: String, subscriptionId: Int) {
refreshedSinceSent = false
try {
ensureBackgroundThread {
val messageId = scheduledMessage?.id ?: generateRandomMessageId()
val message = buildScheduledMessage(text, subscriptionId, messageId)
messagesDB.insertOrUpdate(message)
scheduleMessage(message)
}
clearCurrentMessage()
hideScheduleSendUi()
if (!refreshedSinceSent) {
refreshMessages()
}
} catch (e: Exception) {
showErrorToast(e.localizedMessage ?: getString(R.string.unknown_error_occurred))
}
}
private fun sendNormalMessage(text: String, subscriptionId: Int) {
val addresses = participants.getAddresses()
val attachments = attachmentSelections.values
.map { it.uri }
try { try {
refreshedSinceSent = false refreshedSinceSent = false
sendTransactionMessage(text, addresses, currentSubscriptionId, attachments) sendMessage(text, addresses, subscriptionId, attachments)
clearCurrentMessage()
thread_type_message.setText("")
attachmentSelections.clear()
thread_attachments_holder.beGone()
thread_attachments_wrapper.removeAllViews()
if (!refreshedSinceSent) { if (!refreshedSinceSent) {
refreshMessages() refreshMessages()
@@ -965,6 +989,13 @@ class ThreadActivity : SimpleActivity() {
} }
} }
private fun clearCurrentMessage() {
thread_type_message.setText("")
attachmentSelections.clear()
thread_attachments_holder.beGone()
thread_attachments_wrapper.removeAllViews()
}
// show selected contacts, properly split to new lines when appropriate // show selected contacts, properly split to new lines when appropriate
// based on https://stackoverflow.com/a/13505029/1967672 // based on https://stackoverflow.com/a/13505029/1967672
private fun showSelectedContact(views: ArrayList<View>) { private fun showSelectedContact(views: ArrayList<View>) {
@@ -1115,10 +1146,10 @@ class ThreadActivity : SimpleActivity() {
messages.filter { !it.isReceivedMessage() && it.id > lastMaxId }.forEach { latestMessage -> messages.filter { !it.isReceivedMessage() && it.id > lastMaxId }.forEach { latestMessage ->
// subscriptionIds seem to be not filled out at sending with multiple SIM cards, so fill it manually // subscriptionIds seem to be not filled out at sending with multiple SIM cards, so fill it manually
if ((subscriptionManagerCompat().activeSubscriptionInfoList?.size ?: 0) > 1) { if ((subscriptionManagerCompat().activeSubscriptionInfoList?.size ?: 0) > 1) {
val SIMId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId val subscriptionId = availableSIMCards.getOrNull(currentSIMCardIndex)?.subscriptionId
if (SIMId != null) { if (subscriptionId != null) {
updateMessageSubscriptionId(latestMessage.id, SIMId) updateMessageSubscriptionId(latestMessage.id, subscriptionId)
latestMessage.subscriptionId = SIMId latestMessage.subscriptionId = subscriptionId
} }
} }
@@ -1129,11 +1160,15 @@ class ThreadActivity : SimpleActivity() {
setupSIMSelector() setupSIMSelector()
} }
private fun updateMessageType() { private fun isMmsMessage(text: String): Boolean {
val text = thread_type_message.text.toString()
val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS
val isLongMmsMessage = isLongMmsMessage(text) && config.sendLongMessageMMS val isLongMmsMessage = isLongMmsMessage(text) && config.sendLongMessageMMS
val stringId = if (attachmentSelections.isNotEmpty() || isGroupMms || isLongMmsMessage) { return attachmentSelections.isNotEmpty() || isGroupMms || isLongMmsMessage
}
private fun updateMessageType() {
val text = thread_type_message.text.toString()
val stringId = if (isMmsMessage(text)) {
R.string.mms R.string.mms
} else { } else {
R.string.sms R.string.sms
@@ -1150,6 +1185,27 @@ class ThreadActivity : SimpleActivity() {
return File.createTempFile("IMG_", ".jpg", outputDirectory) return File.createTempFile("IMG_", ".jpg", outputDirectory)
} }
private fun showScheduledMessageInfo(message: Message) {
// todo: maybe show options to edit, delete, and send the message now
editScheduledMessage(message)
}
private fun editScheduledMessage(message: Message) {
scheduledMessage = message
clearCurrentMessage()
thread_type_message.setText(message.body)
val messageAttachment = message.attachment
if (messageAttachment != null) {
for (attachment in messageAttachment.attachments) {
addAttachment(attachment.getUri())
}
}
scheduledDateTime = DateTime(message.millis())
showScheduleSendUi()
}
private fun launchScheduleSendDialog(originalDt: DateTime? = null) { private fun launchScheduleSendDialog(originalDt: DateTime? = null) {
ScheduleSendDialog(this, originalDt) { newDt -> ScheduleSendDialog(this, originalDt) { newDt ->
if (newDt != null) { if (newDt != null) {
@@ -1175,13 +1231,19 @@ class ThreadActivity : SimpleActivity() {
applyColorFilter(textColor) applyColorFilter(textColor)
setOnClickListener { setOnClickListener {
hideScheduleSendUi() hideScheduleSendUi()
if (scheduledMessage != null) {
ensureBackgroundThread {
messagesDB.delete(scheduledMessage!!.id)
refreshMessages()
}
}
} }
} }
} }
private fun showScheduleSendUi() { private fun showScheduleSendUi() {
isScheduledMessage = true isScheduledMessage = true
updateSendButton() updateSendButtonDrawable()
scheduled_message_holder.beVisible() scheduled_message_holder.beVisible()
scheduled_message_button.text = scheduledDateTime.humanize(this) scheduled_message_button.text = scheduledDateTime.humanize(this)
} }
@@ -1189,10 +1251,10 @@ class ThreadActivity : SimpleActivity() {
private fun hideScheduleSendUi() { private fun hideScheduleSendUi() {
isScheduledMessage = false isScheduledMessage = false
scheduled_message_holder.beGone() scheduled_message_holder.beGone()
updateSendButton() updateSendButtonDrawable()
} }
private fun updateSendButton() { private fun updateSendButtonDrawable() {
val drawableResId = if (isScheduledMessage) { val drawableResId = if (isScheduledMessage) {
R.drawable.ic_schedule_send_vector R.drawable.ic_schedule_send_vector
} else { } else {
@@ -1203,4 +1265,30 @@ class ThreadActivity : SimpleActivity() {
thread_send_message.setCompoundDrawablesWithIntrinsicBounds(null, this, null, null) thread_send_message.setCompoundDrawablesWithIntrinsicBounds(null, this, null, null)
} }
} }
private fun buildScheduledMessage(text: String, subscriptionId: Int, messageId: Long): Message {
return Message(
id = messageId,
body = text,
type = MESSAGE_TYPE_QUEUED,
status = STATUS_NONE,
participants = participants,
date = (scheduledDateTime.millis / 1000).toInt(),
read = false,
threadId = threadId,
isMMS = isMmsMessage(text),
attachment = buildMessageAttachment(text, messageId),
senderName = "",
senderPhotoUri = "",
subscriptionId = subscriptionId,
isScheduled = true
)
}
private fun buildMessageAttachment(text: String, messageId: Long): MessageAttachment {
val attachments = attachmentSelections.values
.map { Attachment(null, messageId, it.uri.toString(), "*/*", 0, 0, "") }
.toArrayList()
return MessageAttachment(messageId, text, attachments)
}
} }

View File

@@ -4,10 +4,10 @@ import android.annotation.SuppressLint
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri import android.net.Uri
import android.telephony.SubscriptionManager
import android.util.TypedValue import android.util.TypedValue
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
@@ -40,7 +40,12 @@ import com.simplemobiletools.smsmessenger.models.*
import kotlinx.android.synthetic.main.item_attachment_image.view.* import kotlinx.android.synthetic.main.item_attachment_image.view.*
import kotlinx.android.synthetic.main.item_attachment_vcard.view.* import kotlinx.android.synthetic.main.item_attachment_vcard.view.*
import kotlinx.android.synthetic.main.item_received_message.view.* import kotlinx.android.synthetic.main.item_received_message.view.*
import kotlinx.android.synthetic.main.item_received_message.view.thread_mesage_attachments_holder
import kotlinx.android.synthetic.main.item_received_message.view.thread_message_body
import kotlinx.android.synthetic.main.item_received_message.view.thread_message_holder
import kotlinx.android.synthetic.main.item_received_message.view.thread_message_play_outline
import kotlinx.android.synthetic.main.item_received_unknown_attachment.view.* import kotlinx.android.synthetic.main.item_received_unknown_attachment.view.*
import kotlinx.android.synthetic.main.item_sent_message.view.*
import kotlinx.android.synthetic.main.item_sent_unknown_attachment.view.* import kotlinx.android.synthetic.main.item_sent_unknown_attachment.view.*
import kotlinx.android.synthetic.main.item_thread_date_time.view.* import kotlinx.android.synthetic.main.item_thread_date_time.view.*
import kotlinx.android.synthetic.main.item_thread_error.view.* import kotlinx.android.synthetic.main.item_thread_error.view.*
@@ -252,29 +257,9 @@ class ThreadAdapter(
thread_message_body.beVisibleIf(message.body.isNotEmpty()) thread_message_body.beVisibleIf(message.body.isNotEmpty())
if (message.isReceivedMessage()) { if (message.isReceivedMessage()) {
thread_message_sender_photo.beVisible() setupReceivedMessageView(view, message)
thread_message_sender_photo.setOnClickListener {
val contact = message.participants.first()
context.getContactFromAddress(contact.phoneNumbers.first().normalizedNumber) {
if (it != null) {
(activity as ThreadActivity).startContactDetailsIntent(it)
}
}
}
thread_message_body.setTextColor(textColor)
thread_message_body.setLinkTextColor(context.getProperPrimaryColor())
if (!activity.isFinishing && !activity.isDestroyed) {
SimpleContactsHelper(context).loadContactImage(message.senderPhotoUri, thread_message_sender_photo, message.senderName)
}
} else { } else {
thread_message_sender_photo?.beGone() setupSentMessageView(view, message)
val background = context.getProperPrimaryColor()
thread_message_body.background.applyColorFilter(background)
val contrastColor = background.getContrastColor()
thread_message_body.setTextColor(contrastColor)
thread_message_body.setLinkTextColor(contrastColor)
} }
thread_message_body.setOnLongClickListener { thread_message_body.setOnLongClickListener {
@@ -304,6 +289,55 @@ class ThreadAdapter(
} }
} }
private fun setupReceivedMessageView(view: View, message: Message) {
view.apply {
thread_message_sender_photo.beVisible()
thread_message_sender_photo.setOnClickListener {
val contact = message.participants.first()
context.getContactFromAddress(contact.phoneNumbers.first().normalizedNumber) {
if (it != null) {
(activity as ThreadActivity).startContactDetailsIntent(it)
}
}
}
thread_message_body.setTextColor(textColor)
thread_message_body.setLinkTextColor(context.getProperPrimaryColor())
if (!activity.isFinishing && !activity.isDestroyed) {
SimpleContactsHelper(context).loadContactImage(message.senderPhotoUri, thread_message_sender_photo, message.senderName)
}
}
}
private fun setupSentMessageView(view: View, message: Message) {
view.apply {
thread_message_sender_photo?.beGone()
val background = context.getProperPrimaryColor()
thread_message_body.background.applyColorFilter(background)
val contrastColor = background.getContrastColor()
thread_message_body.setTextColor(contrastColor)
thread_message_body.setLinkTextColor(contrastColor)
val padding = thread_message_body.paddingStart
if (message.isScheduled) {
thread_message_scheduled_icon.beVisible()
thread_message_scheduled_icon.applyColorFilter(contrastColor)
thread_message_scheduled_icon.onGlobalLayout {
val rightPadding = padding + thread_message_scheduled_icon.measuredWidth
thread_message_body.setPadding(padding, padding, rightPadding, padding)
}
thread_message_body.typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC)
} else {
thread_message_scheduled_icon.beGone()
thread_message_body.setPadding(padding, padding, padding, padding)
thread_message_body.typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
}
}
}
private fun setupImageView(holder: ViewHolder, parent: View, message: Message, attachment: Attachment) { private fun setupImageView(holder: ViewHolder, parent: View, message: Message, attachment: Attachment) {
val mimetype = attachment.mimetype val mimetype = attachment.mimetype
val uri = attachment.getUri() val uri = attachment.getUri()

View File

@@ -1,6 +0,0 @@
package com.simplemobiletools.smsmessenger.extensions
import android.text.TextUtils
import com.simplemobiletools.commons.models.SimpleContact
fun ArrayList<SimpleContact>.getThreadTitle() = TextUtils.join(", ", map { it.name }.toTypedArray())

View File

@@ -30,3 +30,5 @@ fun Map<String, Any>.toContentValues(): ContentValues {
return contentValues return contentValues
} }
fun <T> Collection<T>.toArrayList() = ArrayList(this)

View File

@@ -57,7 +57,7 @@ val Context.messageAttachmentsDB: MessageAttachmentsDao get() = getMessagesDB().
val Context.messagesDB: MessagesDao get() = getMessagesDB().MessagesDao() val Context.messagesDB: MessagesDao get() = getMessagesDB().MessagesDao()
fun Context.getMessages(threadId: Long, getImageResolutions: Boolean, dateFrom: Int = -1): ArrayList<Message> { fun Context.getMessages(threadId: Long, getImageResolutions: Boolean, dateFrom: Int = -1, includeScheduledMessages: Boolean = true): ArrayList<Message> {
val uri = Sms.CONTENT_URI val uri = Sms.CONTENT_URI
val projection = arrayOf( val projection = arrayOf(
Sms._ID, Sms._ID,
@@ -116,8 +116,17 @@ fun Context.getMessages(threadId: Long, getImageResolutions: Boolean, dateFrom:
} }
messages.addAll(getMMS(threadId, getImageResolutions, sortOrder)) messages.addAll(getMMS(threadId, getImageResolutions, sortOrder))
messages = messages.filter { it.participants.isNotEmpty() }
.sortedWith(compareBy<Message> { it.date }.thenBy { it.id }).toMutableList() as ArrayList<Message> if (includeScheduledMessages) {
val scheduledMessages = messagesDB.getScheduledThreadMessages(threadId)
messages.addAll(scheduledMessages)
}
messages = messages
.filter { it.participants.isNotEmpty() }
.filterNot { it.isScheduled && it.millis() < System.currentTimeMillis() }
.sortedWith(compareBy<Message> { it.date }.thenBy { it.id })
.toMutableList() as ArrayList<Message>
return messages return messages
} }

View File

@@ -14,6 +14,6 @@ fun DateTime.humanize(context: Context, now: DateTime = DateTime.now(), pattern:
return if (yearOfCentury().get() > now.yearOfCentury().get()) { return if (yearOfCentury().get() > now.yearOfCentury().get()) {
toString(pattern) toString(pattern)
} else { } else {
DateUtils.getRelativeDateTimeString(context, millis, DateUtils.MINUTE_IN_MILLIS, DateUtils.DAY_IN_MILLIS, 0).toString() DateUtils.getRelativeDateTimeString(context, millis, DateUtils.SECOND_IN_MILLIS, DateUtils.DAY_IN_MILLIS, 0).toString()
} }
} }

View File

@@ -0,0 +1,8 @@
package com.simplemobiletools.smsmessenger.extensions
import android.text.TextUtils
import com.simplemobiletools.commons.models.SimpleContact
fun ArrayList<SimpleContact>.getThreadTitle(): String = TextUtils.join(", ", map { it.name }.toTypedArray()).orEmpty()
fun ArrayList<SimpleContact>.getAddresses() = flatMap { it.phoneNumbers }.map { it.normalizedNumber }

View File

@@ -29,6 +29,7 @@ const val IMPORT_SMS = "import_sms"
const val IMPORT_MMS = "import_mms" const val IMPORT_MMS = "import_mms"
const val WAS_DB_CLEARED = "was_db_cleared_2" const val WAS_DB_CLEARED = "was_db_cleared_2"
const val EXTRA_VCARD_URI = "vcard" const val EXTRA_VCARD_URI = "vcard"
const val SCHEDULED_MESSAGE_ID = "scheduled_message_id"
private const val PATH = "com.simplemobiletools.smsmessenger.action." private const val PATH = "com.simplemobiletools.smsmessenger.action."
const val MARK_AS_READ = PATH + "mark_as_read" const val MARK_AS_READ = PATH + "mark_as_read"

View File

@@ -1,16 +1,28 @@
package com.simplemobiletools.smsmessenger.helpers package com.simplemobiletools.smsmessenger.helpers
import android.app.AlarmManager
import android.app.PendingIntent
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 androidx.core.app.AlarmManagerCompat
import com.klinker.android.send_message.Settings import com.klinker.android.send_message.Settings
import com.klinker.android.send_message.Transaction import com.klinker.android.send_message.Transaction
import com.klinker.android.send_message.Utils import com.klinker.android.send_message.Utils
import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.helpers.isMarshmallowPlus
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.extensions.config import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.models.Message
import com.simplemobiletools.smsmessenger.receivers.ScheduledMessageReceiver
import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver
import com.simplemobiletools.smsmessenger.receivers.SmsStatusSentReceiver import com.simplemobiletools.smsmessenger.receivers.SmsStatusSentReceiver
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import kotlin.math.abs
import kotlin.random.Random
fun Context.getSendMessageSettings(): Settings { fun Context.getSendMessageSettings(): Settings {
val settings = Settings() val settings = Settings()
@@ -22,7 +34,7 @@ fun Context.getSendMessageSettings(): Settings {
return settings return settings
} }
fun Context.sendTransactionMessage(text: String, addresses: List<String>, subscriptionId: Int?, attachments: List<Uri>) { fun Context.sendMessage(text: String, addresses: List<String>, subscriptionId: Int?, attachments: List<Uri>) {
val settings = getSendMessageSettings() val settings = getSendMessageSettings()
if (subscriptionId != null) { if (subscriptionId != null) {
settings.subscriptionId = subscriptionId settings.subscriptionId = subscriptionId
@@ -50,10 +62,35 @@ fun Context.sendTransactionMessage(text: String, addresses: List<String>, subscr
transaction.setExplicitBroadcastForSentSms(smsSentIntent) transaction.setExplicitBroadcastForSentSms(smsSentIntent)
transaction.setExplicitBroadcastForDeliveredSms(deliveredIntent) transaction.setExplicitBroadcastForDeliveredSms(deliveredIntent)
transaction.sendNewMessage(message) Handler(Looper.getMainLooper()).post {
transaction.sendNewMessage(message)
}
}
fun Context.scheduleMessage(message: Message) {
val intent = Intent(this, ScheduledMessageReceiver::class.java)
intent.putExtra(THREAD_ID, message.threadId)
intent.putExtra(SCHEDULED_MESSAGE_ID, message.id)
var flags = PendingIntent.FLAG_UPDATE_CURRENT
if (isMarshmallowPlus()) {
flags = flags or PendingIntent.FLAG_IMMUTABLE
}
val pendingIntent = PendingIntent.getBroadcast(this, message.id.toInt(), intent, flags)
val triggerAtMillis = message.millis()
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent)
} }
fun Context.isLongMmsMessage(text: String): Boolean { fun Context.isLongMmsMessage(text: String): Boolean {
val settings = getSendMessageSettings() val settings = getSendMessageSettings()
return Utils.getNumPages(settings, text) > settings.sendLongAsMmsAfter return Utils.getNumPages(settings, text) > settings.sendLongAsMmsAfter
} }
/** Not to be used with real messages persisted in the telephony db. This is for internal use only (e.g. scheduled messages). */
fun generateRandomMessageId(length: Int = 8): Long {
val millis = DateTime.now(DateTimeZone.UTC).millis
val random = abs(Random(millis).nextLong())
return random.toString().takeLast(length).toLong()
}

View File

@@ -3,9 +3,50 @@ package com.simplemobiletools.smsmessenger.receivers
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.PowerManager
import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.extensions.getAddresses
import com.simplemobiletools.smsmessenger.extensions.messagesDB
import com.simplemobiletools.smsmessenger.helpers.SCHEDULED_MESSAGE_ID
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
import com.simplemobiletools.smsmessenger.helpers.sendMessage
class ScheduledMessageReceiver: BroadcastReceiver() { class ScheduledMessageReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
TODO("Not yet implemented") 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)
ensureBackgroundThread {
handleIntent(context, intent)
}
}
private fun handleIntent(context: Context, intent: Intent) {
val threadId = intent.getLongExtra(THREAD_ID, 0L)
val messageId = intent.getLongExtra(SCHEDULED_MESSAGE_ID, 0L)
val message = try {
context.messagesDB.getScheduledMessageWithId(threadId, messageId)
} catch (e: Exception) {
return
}
val addresses = message.participants.getAddresses()
val attachments = message.attachment?.attachments?.mapNotNull { it.getUri() } ?: emptyList()
try {
context.sendMessage(message.body, addresses, message.subscriptionId, attachments)
context.messagesDB.delete(messageId)
refreshMessages()
} catch (e: Exception) {
context.showErrorToast(e)
} catch (e: Error) {
context.showErrorToast(e.localizedMessage ?: context.getString(R.string.unknown_error_occurred))
}
} }
} }

View File

@@ -48,4 +48,15 @@
android:textSize="@dimen/normal_text_size" android:textSize="@dimen/normal_text_size"
tools:text="Sent message" /> tools:text="Sent message" />
</RelativeLayout> </RelativeLayout>
<ImageView
android:id="@+id/thread_message_scheduled_icon"
android:layout_width="@dimen/small_icon_size"
android:layout_height="@dimen/small_icon_size"
android:layout_margin="@dimen/tiny_margin"
android:src="@drawable/ic_clock_vector"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -8,4 +8,5 @@
<dimen name="pin_icon_size">15dp</dimen> <dimen name="pin_icon_size">15dp</dimen>
<dimen name="vcard_property_start_margin">64dp</dimen> <dimen name="vcard_property_start_margin">64dp</dimen>
<dimen name="date_time_text_size">36sp</dimen> <dimen name="date_time_text_size">36sp</dimen>
<dimen name="small_icon_size">20dp</dimen>
</resources> </resources>