mirror of
https://github.com/SimpleMobileTools/Simple-SMS-Messenger.git
synced 2025-06-05 21:49:22 +02:00
Merge pull request #543 from Naveen3Singh/rewrite_sms
Rewrite SMS related code into the app
This commit is contained in:
@ -4,6 +4,7 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
|
import android.widget.Toast
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.reddit.indicatorfastscroll.FastScrollItemIndicator
|
import com.reddit.indicatorfastscroll.FastScrollItemIndicator
|
||||||
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
|
import com.simplemobiletools.commons.dialogs.RadioGroupDialog
|
||||||
@ -16,7 +17,7 @@ import com.simplemobiletools.smsmessenger.adapters.ContactsAdapter
|
|||||||
import com.simplemobiletools.smsmessenger.extensions.getSuggestedContacts
|
import com.simplemobiletools.smsmessenger.extensions.getSuggestedContacts
|
||||||
import com.simplemobiletools.smsmessenger.extensions.getThreadId
|
import com.simplemobiletools.smsmessenger.extensions.getThreadId
|
||||||
import com.simplemobiletools.smsmessenger.helpers.*
|
import com.simplemobiletools.smsmessenger.helpers.*
|
||||||
import kotlinx.android.synthetic.main.activity_main.*
|
import com.simplemobiletools.smsmessenger.messaging.isShortCodeWithLetters
|
||||||
import kotlinx.android.synthetic.main.activity_new_conversation.*
|
import kotlinx.android.synthetic.main.activity_new_conversation.*
|
||||||
import kotlinx.android.synthetic.main.item_suggested_contact.view.*
|
import kotlinx.android.synthetic.main.item_suggested_contact.view.*
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
@ -79,6 +80,11 @@ class NewConversationActivity : SimpleActivity() {
|
|||||||
new_conversation_confirm.applyColorFilter(getProperTextColor())
|
new_conversation_confirm.applyColorFilter(getProperTextColor())
|
||||||
new_conversation_confirm.setOnClickListener {
|
new_conversation_confirm.setOnClickListener {
|
||||||
val number = new_conversation_address.value
|
val number = new_conversation_address.value
|
||||||
|
if (isShortCodeWithLetters(number)) {
|
||||||
|
new_conversation_address.setText("")
|
||||||
|
toast(R.string.invalid_short_code, length = Toast.LENGTH_LONG)
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
launchThreadActivity(number, number)
|
launchThreadActivity(number, number)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,15 +51,19 @@ import com.simplemobiletools.smsmessenger.R
|
|||||||
import com.simplemobiletools.smsmessenger.adapters.AttachmentsAdapter
|
import com.simplemobiletools.smsmessenger.adapters.AttachmentsAdapter
|
||||||
import com.simplemobiletools.smsmessenger.adapters.AutoCompleteTextViewAdapter
|
import com.simplemobiletools.smsmessenger.adapters.AutoCompleteTextViewAdapter
|
||||||
import com.simplemobiletools.smsmessenger.adapters.ThreadAdapter
|
import com.simplemobiletools.smsmessenger.adapters.ThreadAdapter
|
||||||
|
import com.simplemobiletools.smsmessenger.dialogs.InvalidNumberDialog
|
||||||
import com.simplemobiletools.smsmessenger.dialogs.RenameConversationDialog
|
import com.simplemobiletools.smsmessenger.dialogs.RenameConversationDialog
|
||||||
import com.simplemobiletools.smsmessenger.dialogs.ScheduleMessageDialog
|
import com.simplemobiletools.smsmessenger.dialogs.ScheduleMessageDialog
|
||||||
import com.simplemobiletools.smsmessenger.extensions.*
|
import com.simplemobiletools.smsmessenger.extensions.*
|
||||||
import com.simplemobiletools.smsmessenger.helpers.*
|
import com.simplemobiletools.smsmessenger.helpers.*
|
||||||
|
import com.simplemobiletools.smsmessenger.messaging.*
|
||||||
import com.simplemobiletools.smsmessenger.models.*
|
import com.simplemobiletools.smsmessenger.models.*
|
||||||
import com.simplemobiletools.smsmessenger.models.ThreadItem.*
|
import com.simplemobiletools.smsmessenger.models.ThreadItem.*
|
||||||
import kotlinx.android.synthetic.main.activity_thread.*
|
import kotlinx.android.synthetic.main.activity_thread.*
|
||||||
import kotlinx.android.synthetic.main.item_selected_contact.view.*
|
import kotlinx.android.synthetic.main.item_selected_contact.view.*
|
||||||
import kotlinx.android.synthetic.main.layout_attachment_picker.*
|
import kotlinx.android.synthetic.main.layout_attachment_picker.*
|
||||||
|
import kotlinx.android.synthetic.main.layout_invalid_short_code_info.*
|
||||||
|
import kotlinx.android.synthetic.main.layout_thread_send_message_holder.*
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
@ -176,6 +180,7 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thread_send_message_holder.setBackgroundColor(bottomBarColor)
|
thread_send_message_holder.setBackgroundColor(bottomBarColor)
|
||||||
|
reply_disabled_info_holder.setBackgroundColor(bottomBarColor)
|
||||||
updateNavigationBarColor(bottomBarColor)
|
updateNavigationBarColor(bottomBarColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,7 +219,8 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
findItem(R.id.conversation_details).isVisible = participants.size > 1 && conversation != null
|
findItem(R.id.conversation_details).isVisible = participants.size > 1 && conversation != null
|
||||||
findItem(R.id.block_number).title = addLockedLabelIfNeeded(R.string.block_number)
|
findItem(R.id.block_number).title = addLockedLabelIfNeeded(R.string.block_number)
|
||||||
findItem(R.id.block_number).isVisible = isNougatPlus()
|
findItem(R.id.block_number).isVisible = isNougatPlus()
|
||||||
findItem(R.id.dial_number).isVisible = participants.size == 1
|
findItem(R.id.dial_number).isVisible = participants.size == 1 && !isSpecialNumber()
|
||||||
|
findItem(R.id.manage_people).isVisible = !isSpecialNumber()
|
||||||
findItem(R.id.mark_as_unread).isVisible = threadItems.isNotEmpty()
|
findItem(R.id.mark_as_unread).isVisible = threadItems.isNotEmpty()
|
||||||
|
|
||||||
// allow saving number in cases when we dont have it stored yet and it is a casual readable number
|
// allow saving number in cases when we dont have it stored yet and it is a casual readable number
|
||||||
@ -670,6 +676,35 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
} else {
|
} else {
|
||||||
messages.first().participants
|
messages.first().participants
|
||||||
}
|
}
|
||||||
|
runOnUiThread {
|
||||||
|
maybeDisableShortCodeReply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isSpecialNumber(): Boolean {
|
||||||
|
val addresses = participants.getAddresses()
|
||||||
|
return addresses.any { isShortCodeWithLetters(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun maybeDisableShortCodeReply() {
|
||||||
|
if (isSpecialNumber()) {
|
||||||
|
thread_send_message_holder.beGone()
|
||||||
|
reply_disabled_info_holder.beVisible()
|
||||||
|
val textColor = getProperTextColor()
|
||||||
|
reply_disabled_text.setTextColor(textColor)
|
||||||
|
reply_disabled_info.apply {
|
||||||
|
applyColorFilter(textColor)
|
||||||
|
setOnClickListener {
|
||||||
|
InvalidNumberDialog(
|
||||||
|
activity = this@ThreadActivity,
|
||||||
|
text = getString(R.string.invalid_short_code_desc)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (isOreoPlus()) {
|
||||||
|
tooltipText = getString(R.string.more_info)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1167,7 +1202,7 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
refreshedSinceSent = false
|
refreshedSinceSent = false
|
||||||
sendMessage(text, addresses, subscriptionId, attachments)
|
sendMessageCompat(text, addresses, subscriptionId, attachments)
|
||||||
ensureBackgroundThread {
|
ensureBackgroundThread {
|
||||||
val messageIds = messages.map { it.id }
|
val messageIds = messages.map { it.id }
|
||||||
val message = getMessages(threadId, getImageResolutions = true, limit = 1).firstOrNull { it.id !in messageIds }
|
val message = getMessages(threadId, getImageResolutions = true, limit = 1).firstOrNull { it.id !in messageIds }
|
||||||
@ -1347,7 +1382,7 @@ class ThreadActivity : SimpleActivity() {
|
|||||||
|
|
||||||
private fun isMmsMessage(text: String): Boolean {
|
private fun isMmsMessage(text: String): Boolean {
|
||||||
val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS
|
val isGroupMms = participants.size > 1 && config.sendGroupMessageMMS
|
||||||
val isLongMmsMessage = isLongMmsMessage(text) && config.sendLongMessageMMS
|
val isLongMmsMessage = isLongMmsMessage(text)
|
||||||
return getAttachmentSelections().isNotEmpty() || isGroupMms || isLongMmsMessage
|
return getAttachmentSelections().isNotEmpty() || isGroupMms || isLongMmsMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import com.simplemobiletools.smsmessenger.activities.SimpleActivity
|
|||||||
import com.simplemobiletools.smsmessenger.dialogs.RenameConversationDialog
|
import com.simplemobiletools.smsmessenger.dialogs.RenameConversationDialog
|
||||||
import com.simplemobiletools.smsmessenger.extensions.*
|
import com.simplemobiletools.smsmessenger.extensions.*
|
||||||
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
|
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
|
||||||
|
import com.simplemobiletools.smsmessenger.messaging.isShortCodeWithLetters
|
||||||
import com.simplemobiletools.smsmessenger.models.Conversation
|
import com.simplemobiletools.smsmessenger.models.Conversation
|
||||||
import kotlinx.android.synthetic.main.item_conversation.view.*
|
import kotlinx.android.synthetic.main.item_conversation.view.*
|
||||||
|
|
||||||
@ -57,14 +58,17 @@ class ConversationsAdapter(
|
|||||||
|
|
||||||
override fun prepareActionMode(menu: Menu) {
|
override fun prepareActionMode(menu: Menu) {
|
||||||
val selectedItems = getSelectedItems()
|
val selectedItems = getSelectedItems()
|
||||||
|
val isSingleSelection = isOneItemSelected()
|
||||||
|
val selectedConversation = selectedItems.firstOrNull()
|
||||||
|
val isGroupConversation = selectedConversation?.isGroupConversation == true
|
||||||
|
|
||||||
menu.apply {
|
menu.apply {
|
||||||
findItem(R.id.cab_block_number).title = activity.addLockedLabelIfNeeded(R.string.block_number)
|
findItem(R.id.cab_block_number).title = activity.addLockedLabelIfNeeded(R.string.block_number)
|
||||||
findItem(R.id.cab_block_number).isVisible = isNougatPlus()
|
findItem(R.id.cab_block_number).isVisible = isNougatPlus()
|
||||||
findItem(R.id.cab_add_number_to_contact).isVisible = isOneItemSelected() && selectedItems.firstOrNull()?.isGroupConversation == false
|
findItem(R.id.cab_add_number_to_contact).isVisible = isSingleSelection && !isGroupConversation
|
||||||
findItem(R.id.cab_dial_number).isVisible = isOneItemSelected() && selectedItems.firstOrNull()?.isGroupConversation == false
|
findItem(R.id.cab_dial_number).isVisible = isSingleSelection && !isGroupConversation && !isShortCodeWithLetters(selectedConversation!!.phoneNumber)
|
||||||
findItem(R.id.cab_copy_number).isVisible = isOneItemSelected() && selectedItems.firstOrNull()?.isGroupConversation == false
|
findItem(R.id.cab_copy_number).isVisible = isSingleSelection && !isGroupConversation
|
||||||
findItem(R.id.rename_conversation).isVisible = isOneItemSelected() && selectedItems.firstOrNull()?.isGroupConversation == true
|
findItem(R.id.rename_conversation).isVisible = isSingleSelection && isGroupConversation
|
||||||
findItem(R.id.cab_mark_as_read).isVisible = selectedItems.any { !it.read }
|
findItem(R.id.cab_mark_as_read).isVisible = selectedItems.any { !it.read }
|
||||||
findItem(R.id.cab_mark_as_unread).isVisible = selectedItems.any { it.read }
|
findItem(R.id.cab_mark_as_unread).isVisible = selectedItems.any { it.read }
|
||||||
checkPinBtnVisibility(this)
|
checkPinBtnVisibility(this)
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.simplemobiletools.smsmessenger.dialogs
|
||||||
|
|
||||||
|
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||||
|
import com.simplemobiletools.commons.extensions.getAlertDialogBuilder
|
||||||
|
import com.simplemobiletools.commons.extensions.setupDialogStuff
|
||||||
|
import com.simplemobiletools.smsmessenger.R
|
||||||
|
import kotlinx.android.synthetic.main.dialog_invalid_number.view.*
|
||||||
|
|
||||||
|
class InvalidNumberDialog(val activity: BaseSimpleActivity, val text: String) {
|
||||||
|
init {
|
||||||
|
val view = activity.layoutInflater.inflate(R.layout.dialog_invalid_number, null).apply {
|
||||||
|
dialog_invalid_number_desc.text = text
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.getAlertDialogBuilder()
|
||||||
|
.setPositiveButton(R.string.ok) { _, _ -> { } }
|
||||||
|
.apply {
|
||||||
|
activity.setupDialogStuff(view, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package com.simplemobiletools.smsmessenger.extensions
|
package com.simplemobiletools.smsmessenger.extensions
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Application
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
@ -18,7 +19,6 @@ import android.text.TextUtils
|
|||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.klinker.android.send_message.Transaction.getAddressSeparator
|
|
||||||
import com.simplemobiletools.commons.extensions.*
|
import com.simplemobiletools.commons.extensions.*
|
||||||
import com.simplemobiletools.commons.helpers.*
|
import com.simplemobiletools.commons.helpers.*
|
||||||
import com.simplemobiletools.commons.models.PhoneNumber
|
import com.simplemobiletools.commons.models.PhoneNumber
|
||||||
@ -31,6 +31,9 @@ import com.simplemobiletools.smsmessenger.interfaces.AttachmentsDao
|
|||||||
import com.simplemobiletools.smsmessenger.interfaces.ConversationsDao
|
import com.simplemobiletools.smsmessenger.interfaces.ConversationsDao
|
||||||
import com.simplemobiletools.smsmessenger.interfaces.MessageAttachmentsDao
|
import com.simplemobiletools.smsmessenger.interfaces.MessageAttachmentsDao
|
||||||
import com.simplemobiletools.smsmessenger.interfaces.MessagesDao
|
import com.simplemobiletools.smsmessenger.interfaces.MessagesDao
|
||||||
|
import com.simplemobiletools.smsmessenger.messaging.MessagingUtils
|
||||||
|
import com.simplemobiletools.smsmessenger.messaging.MessagingUtils.Companion.ADDRESS_SEPARATOR
|
||||||
|
import com.simplemobiletools.smsmessenger.messaging.SmsSender
|
||||||
import com.simplemobiletools.smsmessenger.models.*
|
import com.simplemobiletools.smsmessenger.models.*
|
||||||
import me.leolin.shortcutbadger.ShortcutBadger
|
import me.leolin.shortcutbadger.ShortcutBadger
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
@ -49,6 +52,10 @@ val Context.messagesDB: MessagesDao get() = getMessagesDB().MessagesDao()
|
|||||||
|
|
||||||
val Context.notificationHelper get() = NotificationHelper(this)
|
val Context.notificationHelper get() = NotificationHelper(this)
|
||||||
|
|
||||||
|
val Context.messagingUtils get() = MessagingUtils(this)
|
||||||
|
|
||||||
|
val Context.smsSender get() = SmsSender.getInstance(applicationContext as Application)
|
||||||
|
|
||||||
fun Context.getMessages(
|
fun Context.getMessages(
|
||||||
threadId: Long,
|
threadId: Long,
|
||||||
getImageResolutions: Boolean,
|
getImageResolutions: Boolean,
|
||||||
@ -103,7 +110,7 @@ fun Context.getMessages(
|
|||||||
val thread = cursor.getLongValue(Sms.THREAD_ID)
|
val thread = cursor.getLongValue(Sms.THREAD_ID)
|
||||||
val subscriptionId = cursor.getIntValue(Sms.SUBSCRIPTION_ID)
|
val subscriptionId = cursor.getIntValue(Sms.SUBSCRIPTION_ID)
|
||||||
val status = cursor.getIntValue(Sms.STATUS)
|
val status = cursor.getIntValue(Sms.STATUS)
|
||||||
val participants = senderNumber.split(getAddressSeparator()).map { number ->
|
val participants = senderNumber.split(ADDRESS_SEPARATOR).map { number ->
|
||||||
val phoneNumber = PhoneNumber(number, 0, "", number)
|
val phoneNumber = PhoneNumber(number, 0, "", number)
|
||||||
val participantPhoto = getNameAndPhotoFromPhoneNumber(number)
|
val participantPhoto = getNameAndPhotoFromPhoneNumber(number)
|
||||||
SimpleContact(0, 0, participantPhoto.name, photoUri, arrayListOf(phoneNumber), ArrayList(), ArrayList())
|
SimpleContact(0, 0, participantPhoto.name, photoUri, arrayListOf(phoneNumber), ArrayList(), ArrayList())
|
||||||
@ -648,26 +655,6 @@ fun Context.markThreadMessagesUnread(threadId: Long) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.updateMessageType(id: Long, type: Int) {
|
|
||||||
val uri = Sms.CONTENT_URI
|
|
||||||
val contentValues = ContentValues().apply {
|
|
||||||
put(Sms.TYPE, type)
|
|
||||||
}
|
|
||||||
val selection = "${Sms._ID} = ?"
|
|
||||||
val selectionArgs = arrayOf(id.toString())
|
|
||||||
contentResolver.update(uri, contentValues, selection, selectionArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.updateMessageStatus(id: Long, status: Int) {
|
|
||||||
val uri = Sms.CONTENT_URI
|
|
||||||
val contentValues = ContentValues().apply {
|
|
||||||
put(Sms.STATUS, status)
|
|
||||||
}
|
|
||||||
val selection = "${Sms._ID} = ?"
|
|
||||||
val selectionArgs = arrayOf(id.toString())
|
|
||||||
contentResolver.update(uri, contentValues, selection, selectionArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.updateUnreadCountBadge(conversations: List<Conversation>) {
|
fun Context.updateUnreadCountBadge(conversations: List<Conversation>) {
|
||||||
val unreadCount = conversations.count { !it.read }
|
val unreadCount = conversations.count { !it.read }
|
||||||
if (unreadCount == 0) {
|
if (unreadCount == 0) {
|
||||||
|
@ -2,6 +2,10 @@ package com.simplemobiletools.smsmessenger.helpers
|
|||||||
|
|
||||||
import com.simplemobiletools.smsmessenger.models.Events
|
import com.simplemobiletools.smsmessenger.models.Events
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import org.joda.time.DateTimeZone
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
const val THREAD_ID = "thread_id"
|
const val THREAD_ID = "thread_id"
|
||||||
const val THREAD_TITLE = "thread_title"
|
const val THREAD_TITLE = "thread_title"
|
||||||
@ -81,3 +85,10 @@ const val PICK_CONTACT_INTENT = 48
|
|||||||
fun refreshMessages() {
|
fun refreshMessages() {
|
||||||
EventBus.getDefault().post(Events.RefreshMessages())
|
EventBus.getDefault().post(Events.RefreshMessages())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Not to be used with real messages persisted in the telephony db. This is for internal use only (e.g. scheduled messages, notification ids etc). */
|
||||||
|
fun generateRandomId(length: Int = 9): Long {
|
||||||
|
val millis = DateTime.now(DateTimeZone.UTC).millis
|
||||||
|
val random = abs(Random(millis).nextLong())
|
||||||
|
return random.toString().takeLast(length).toLong()
|
||||||
|
}
|
||||||
|
@ -1,111 +0,0 @@
|
|||||||
package com.simplemobiletools.smsmessenger.helpers
|
|
||||||
|
|
||||||
import android.app.AlarmManager
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import androidx.core.app.AlarmManagerCompat
|
|
||||||
import com.klinker.android.send_message.Settings
|
|
||||||
import com.klinker.android.send_message.Transaction
|
|
||||||
import com.klinker.android.send_message.Utils
|
|
||||||
import com.simplemobiletools.commons.extensions.showErrorToast
|
|
||||||
import com.simplemobiletools.smsmessenger.R
|
|
||||||
import com.simplemobiletools.smsmessenger.extensions.config
|
|
||||||
import com.simplemobiletools.smsmessenger.extensions.isPlainTextMimeType
|
|
||||||
import com.simplemobiletools.smsmessenger.models.Attachment
|
|
||||||
import com.simplemobiletools.smsmessenger.models.Message
|
|
||||||
import com.simplemobiletools.smsmessenger.receivers.ScheduledMessageReceiver
|
|
||||||
import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver
|
|
||||||
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 {
|
|
||||||
val settings = Settings()
|
|
||||||
settings.useSystemSending = true
|
|
||||||
settings.deliveryReports = config.enableDeliveryReports
|
|
||||||
settings.sendLongAsMms = config.sendLongMessageMMS
|
|
||||||
settings.sendLongAsMmsAfter = 1
|
|
||||||
settings.group = config.sendGroupMessageMMS
|
|
||||||
return settings
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.sendMessage(text: String, addresses: List<String>, subscriptionId: Int?, attachments: List<Attachment>) {
|
|
||||||
val settings = getSendMessageSettings()
|
|
||||||
if (subscriptionId != null) {
|
|
||||||
settings.subscriptionId = subscriptionId
|
|
||||||
}
|
|
||||||
|
|
||||||
val transaction = Transaction(this, settings)
|
|
||||||
val message = com.klinker.android.send_message.Message(text, addresses.toTypedArray())
|
|
||||||
|
|
||||||
if (attachments.isNotEmpty()) {
|
|
||||||
for (attachment in attachments) {
|
|
||||||
try {
|
|
||||||
val uri = attachment.getUri()
|
|
||||||
contentResolver.openInputStream(uri)?.use {
|
|
||||||
val bytes = it.readBytes()
|
|
||||||
val mimeType = if (attachment.mimetype.isPlainTextMimeType()) {
|
|
||||||
"application/txt"
|
|
||||||
} else {
|
|
||||||
attachment.mimetype
|
|
||||||
}
|
|
||||||
val name = attachment.filename
|
|
||||||
message.addMedia(bytes, mimeType, name, name)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
showErrorToast(e)
|
|
||||||
} catch (e: Error) {
|
|
||||||
showErrorToast(e.localizedMessage ?: getString(R.string.unknown_error_occurred))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val smsSentIntent = Intent(this, SmsStatusSentReceiver::class.java)
|
|
||||||
val deliveredIntent = Intent(this, SmsStatusDeliveredReceiver::class.java)
|
|
||||||
|
|
||||||
transaction.setExplicitBroadcastForSentSms(smsSentIntent)
|
|
||||||
transaction.setExplicitBroadcastForDeliveredSms(deliveredIntent)
|
|
||||||
try {
|
|
||||||
transaction.sendNewMessage(message)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
showErrorToast(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.getScheduleSendPendingIntent(message: Message): PendingIntent {
|
|
||||||
val intent = Intent(this, ScheduledMessageReceiver::class.java)
|
|
||||||
intent.putExtra(THREAD_ID, message.threadId)
|
|
||||||
intent.putExtra(SCHEDULED_MESSAGE_ID, message.id)
|
|
||||||
|
|
||||||
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
||||||
return PendingIntent.getBroadcast(this, message.id.toInt(), intent, flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.scheduleMessage(message: Message) {
|
|
||||||
val pendingIntent = getScheduleSendPendingIntent(message)
|
|
||||||
val triggerAtMillis = message.millis()
|
|
||||||
|
|
||||||
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
|
||||||
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.cancelScheduleSendPendingIntent(messageId: Long) {
|
|
||||||
val intent = Intent(this, ScheduledMessageReceiver::class.java)
|
|
||||||
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
||||||
PendingIntent.getBroadcast(this, messageId.toInt(), intent, flags).cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.isLongMmsMessage(text: String): Boolean {
|
|
||||||
val settings = getSendMessageSettings()
|
|
||||||
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 generateRandomId(length: Int = 9): Long {
|
|
||||||
val millis = DateTime.now(DateTimeZone.UTC).millis
|
|
||||||
val random = abs(Random(millis).nextLong())
|
|
||||||
return random.toString().takeLast(length).toLong()
|
|
||||||
}
|
|
@ -22,6 +22,7 @@ import com.simplemobiletools.commons.helpers.isOreoPlus
|
|||||||
import com.simplemobiletools.smsmessenger.R
|
import com.simplemobiletools.smsmessenger.R
|
||||||
import com.simplemobiletools.smsmessenger.activities.ThreadActivity
|
import com.simplemobiletools.smsmessenger.activities.ThreadActivity
|
||||||
import com.simplemobiletools.smsmessenger.extensions.config
|
import com.simplemobiletools.smsmessenger.extensions.config
|
||||||
|
import com.simplemobiletools.smsmessenger.messaging.isShortCodeWithLetters
|
||||||
import com.simplemobiletools.smsmessenger.receivers.DirectReplyReceiver
|
import com.simplemobiletools.smsmessenger.receivers.DirectReplyReceiver
|
||||||
import com.simplemobiletools.smsmessenger.receivers.MarkAsReadReceiver
|
import com.simplemobiletools.smsmessenger.receivers.MarkAsReadReceiver
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ class NotificationHelper(private val context: Context) {
|
|||||||
PendingIntent.getBroadcast(context, notificationId, markAsReadIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
|
PendingIntent.getBroadcast(context, notificationId, markAsReadIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
|
||||||
|
|
||||||
var replyAction: NotificationCompat.Action? = null
|
var replyAction: NotificationCompat.Action? = null
|
||||||
if (isNougatPlus()) {
|
if (isNougatPlus() && !isShortCodeWithLetters(address)) {
|
||||||
val replyLabel = context.getString(R.string.reply)
|
val replyLabel = context.getString(R.string.reply)
|
||||||
val remoteInput = RemoteInput.Builder(REPLY)
|
val remoteInput = RemoteInput.Builder(REPLY)
|
||||||
.setLabel(replyLabel)
|
.setLabel(replyLabel)
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
package com.simplemobiletools.smsmessenger.messaging
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.telephony.SmsMessage
|
||||||
|
import android.widget.Toast.LENGTH_LONG
|
||||||
|
import com.klinker.android.send_message.Settings
|
||||||
|
import com.simplemobiletools.commons.extensions.showErrorToast
|
||||||
|
import com.simplemobiletools.commons.extensions.toast
|
||||||
|
import com.simplemobiletools.smsmessenger.R
|
||||||
|
import com.simplemobiletools.smsmessenger.extensions.config
|
||||||
|
import com.simplemobiletools.smsmessenger.extensions.messagingUtils
|
||||||
|
import com.simplemobiletools.smsmessenger.messaging.SmsException.Companion.EMPTY_DESTINATION_ADDRESS
|
||||||
|
import com.simplemobiletools.smsmessenger.messaging.SmsException.Companion.ERROR_PERSISTING_MESSAGE
|
||||||
|
import com.simplemobiletools.smsmessenger.messaging.SmsException.Companion.ERROR_SENDING_MESSAGE
|
||||||
|
import com.simplemobiletools.smsmessenger.models.Attachment
|
||||||
|
|
||||||
|
@Deprecated("TODO: Move/rewrite messaging config code into the app.")
|
||||||
|
fun Context.getSendMessageSettings(): Settings {
|
||||||
|
val settings = Settings()
|
||||||
|
settings.useSystemSending = true
|
||||||
|
settings.deliveryReports = config.enableDeliveryReports
|
||||||
|
settings.sendLongAsMms = config.sendLongMessageMMS
|
||||||
|
settings.sendLongAsMmsAfter = 1
|
||||||
|
settings.group = config.sendGroupMessageMMS
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.isLongMmsMessage(text: String, settings: Settings = getSendMessageSettings()): Boolean {
|
||||||
|
val data = SmsMessage.calculateLength(text, false)
|
||||||
|
val numPages = data.first()
|
||||||
|
return numPages > settings.sendLongAsMmsAfter && settings.sendLongAsMms
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sends the message using the in-app SmsManager API wrappers if it's an SMS or using android-smsmms for MMS. */
|
||||||
|
fun Context.sendMessageCompat(text: String, addresses: List<String>, subId: Int?, attachments: List<Attachment>) {
|
||||||
|
val settings = getSendMessageSettings()
|
||||||
|
if (subId != null) {
|
||||||
|
settings.subscriptionId = subId
|
||||||
|
}
|
||||||
|
|
||||||
|
val isMms = attachments.isNotEmpty() || isLongMmsMessage(text, settings) || addresses.size > 1 && settings.group
|
||||||
|
if (isMms) {
|
||||||
|
messagingUtils.sendMmsMessage(text, addresses, attachments, settings)
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
messagingUtils.sendSmsMessage(text, addresses.toSet(), settings.subscriptionId, settings.deliveryReports)
|
||||||
|
} catch (e: SmsException) {
|
||||||
|
when (e.errorCode) {
|
||||||
|
EMPTY_DESTINATION_ADDRESS -> toast(id = R.string.empty_destination_address, length = LENGTH_LONG)
|
||||||
|
ERROR_PERSISTING_MESSAGE -> toast(id = R.string.unable_to_save_message, length = LENGTH_LONG)
|
||||||
|
ERROR_SENDING_MESSAGE -> toast(
|
||||||
|
msg = getString(R.string.unknown_error_occurred_sending_message, e.errorCode),
|
||||||
|
length = LENGTH_LONG
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
showErrorToast(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a given "address" is a short code.
|
||||||
|
* There's not much info available on these special numbers, even the wikipedia page (https://en.wikipedia.org/wiki/Short_code)
|
||||||
|
* contains outdated information regarding max number of digits. The exact parameters for short codes can vary by country and by carrier.
|
||||||
|
*
|
||||||
|
* This function simply returns true if the [address] contains at least one letter.
|
||||||
|
*/
|
||||||
|
fun isShortCodeWithLetters(address: String): Boolean {
|
||||||
|
return address.any { it.isLetter() }
|
||||||
|
}
|
@ -0,0 +1,192 @@
|
|||||||
|
package com.simplemobiletools.smsmessenger.messaging
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.Telephony.Sms
|
||||||
|
import android.telephony.SmsManager
|
||||||
|
import android.telephony.SmsMessage
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.klinker.android.send_message.Message
|
||||||
|
import com.klinker.android.send_message.Settings
|
||||||
|
import com.klinker.android.send_message.Transaction
|
||||||
|
import com.simplemobiletools.commons.extensions.showErrorToast
|
||||||
|
import com.simplemobiletools.commons.extensions.toast
|
||||||
|
import com.simplemobiletools.smsmessenger.R
|
||||||
|
import com.simplemobiletools.smsmessenger.extensions.getThreadId
|
||||||
|
import com.simplemobiletools.smsmessenger.extensions.isPlainTextMimeType
|
||||||
|
import com.simplemobiletools.smsmessenger.extensions.smsSender
|
||||||
|
import com.simplemobiletools.smsmessenger.messaging.SmsException.Companion.ERROR_PERSISTING_MESSAGE
|
||||||
|
import com.simplemobiletools.smsmessenger.models.Attachment
|
||||||
|
import com.simplemobiletools.smsmessenger.receivers.MmsSentReceiver
|
||||||
|
import com.simplemobiletools.smsmessenger.receivers.SendStatusReceiver
|
||||||
|
|
||||||
|
class MessagingUtils(val context: Context) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert an SMS to the given URI with thread_id specified.
|
||||||
|
*/
|
||||||
|
private fun insertSmsMessage(
|
||||||
|
subId: Int, dest: String, text: String, timestamp: Long, threadId: Long,
|
||||||
|
status: Int = Sms.STATUS_NONE, type: Int = Sms.MESSAGE_TYPE_OUTBOX
|
||||||
|
): Uri {
|
||||||
|
val response: Uri?
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(Sms.ADDRESS, dest)
|
||||||
|
put(Sms.DATE, timestamp)
|
||||||
|
put(Sms.READ, 1)
|
||||||
|
put(Sms.SEEN, 1)
|
||||||
|
put(Sms.BODY, text)
|
||||||
|
|
||||||
|
// insert subscription id only if it is a valid one.
|
||||||
|
if (subId != Settings.DEFAULT_SUBSCRIPTION_ID) {
|
||||||
|
put(Sms.SUBSCRIPTION_ID, subId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status != Sms.STATUS_NONE) {
|
||||||
|
put(Sms.STATUS, status)
|
||||||
|
}
|
||||||
|
if (type != Sms.MESSAGE_TYPE_ALL) {
|
||||||
|
put(Sms.TYPE, type)
|
||||||
|
}
|
||||||
|
if (threadId != -1L) {
|
||||||
|
put(Sms.THREAD_ID, threadId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = context.contentResolver.insert(Sms.CONTENT_URI, values)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw SmsException(ERROR_PERSISTING_MESSAGE, e)
|
||||||
|
}
|
||||||
|
return response ?: throw SmsException(ERROR_PERSISTING_MESSAGE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send an SMS message given [text] and [addresses]. A [SmsException] is thrown in case any errors occur. */
|
||||||
|
fun sendSmsMessage(
|
||||||
|
text: String, addresses: Set<String>, subId: Int, requireDeliveryReport: Boolean
|
||||||
|
) {
|
||||||
|
if (addresses.size > 1) {
|
||||||
|
// insert a dummy message for this thread if it is a group message
|
||||||
|
val broadCastThreadId = context.getThreadId(addresses.toSet())
|
||||||
|
val mergedAddresses = addresses.joinToString(ADDRESS_SEPARATOR)
|
||||||
|
insertSmsMessage(
|
||||||
|
subId = subId, dest = mergedAddresses, text = text,
|
||||||
|
timestamp = System.currentTimeMillis(), threadId = broadCastThreadId,
|
||||||
|
status = Sms.Sent.STATUS_COMPLETE, type = Sms.Sent.MESSAGE_TYPE_SENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (address in addresses) {
|
||||||
|
val threadId = context.getThreadId(address)
|
||||||
|
val messageUri = insertSmsMessage(
|
||||||
|
subId = subId, dest = address, text = text,
|
||||||
|
timestamp = System.currentTimeMillis(), threadId = threadId
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
context.smsSender.sendMessage(
|
||||||
|
subId = subId, destination = address, body = text, serviceCenter = null,
|
||||||
|
requireDeliveryReport = requireDeliveryReport, messageUri = messageUri
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
updateSmsMessageSendingStatus(messageUri, Sms.Outbox.MESSAGE_TYPE_FAILED)
|
||||||
|
throw e // propagate error to caller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateSmsMessageSendingStatus(messageUri: Uri?, type: Int) {
|
||||||
|
val resolver = context.contentResolver
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(Sms.Outbox.TYPE, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (messageUri != null) {
|
||||||
|
resolver.update(messageUri, values, null, null)
|
||||||
|
} else {
|
||||||
|
// mark latest sms as sent, need to check if this is still necessary (or reliable)
|
||||||
|
// as this was taken from android-smsmms. The messageUri shouldn't be null anyway
|
||||||
|
val cursor = resolver.query(Sms.Outbox.CONTENT_URI, null, null, null, null)
|
||||||
|
cursor?.use {
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
@SuppressLint("Range")
|
||||||
|
val id = cursor.getString(cursor.getColumnIndex(Sms.Outbox._ID))
|
||||||
|
val selection = "${Sms._ID} = ?"
|
||||||
|
val selectionArgs = arrayOf(id.toString())
|
||||||
|
resolver.update(Sms.Outbox.CONTENT_URI, values, selection, selectionArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
context.showErrorToast(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.")
|
||||||
|
fun sendMmsMessage(text: String, addresses: List<String>, attachments: List<Attachment>, settings: Settings) {
|
||||||
|
val transaction = Transaction(context, settings)
|
||||||
|
val message = Message(text, addresses.toTypedArray())
|
||||||
|
|
||||||
|
if (attachments.isNotEmpty()) {
|
||||||
|
for (attachment in attachments) {
|
||||||
|
try {
|
||||||
|
val uri = attachment.getUri()
|
||||||
|
context.contentResolver.openInputStream(uri)?.use {
|
||||||
|
val bytes = it.readBytes()
|
||||||
|
val mimeType = if (attachment.mimetype.isPlainTextMimeType()) {
|
||||||
|
"application/txt"
|
||||||
|
} else {
|
||||||
|
attachment.mimetype
|
||||||
|
}
|
||||||
|
val name = attachment.filename
|
||||||
|
message.addMedia(bytes, mimeType, name, name)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
context.showErrorToast(e)
|
||||||
|
} catch (e: Error) {
|
||||||
|
context.showErrorToast(e.localizedMessage ?: context.getString(R.string.unknown_error_occurred))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val mmsSentIntent = Intent(context, MmsSentReceiver::class.java)
|
||||||
|
transaction.setExplicitBroadcastForSentMms(mmsSentIntent)
|
||||||
|
|
||||||
|
try {
|
||||||
|
transaction.sendNewMessage(message)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
context.showErrorToast(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun maybeShowErrorToast(resultCode: Int, errorCode: Int) {
|
||||||
|
if (resultCode != Activity.RESULT_OK) {
|
||||||
|
val msg = if (errorCode != SendStatusReceiver.NO_ERROR_CODE) {
|
||||||
|
context.getString(R.string.carrier_send_error)
|
||||||
|
} else {
|
||||||
|
when (resultCode) {
|
||||||
|
SmsManager.RESULT_ERROR_NO_SERVICE -> context.getString(R.string.error_service_is_unavailable)
|
||||||
|
SmsManager.RESULT_ERROR_RADIO_OFF -> context.getString(R.string.error_radio_turned_off)
|
||||||
|
else -> context.getString(R.string.unknown_error_occurred_sending_message, resultCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.toast(msg = msg, length = Toast.LENGTH_LONG)
|
||||||
|
} else {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ADDRESS_SEPARATOR = "|"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.simplemobiletools.smsmessenger.messaging
|
||||||
|
|
||||||
|
import android.app.AlarmManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.app.AlarmManagerCompat
|
||||||
|
import com.simplemobiletools.smsmessenger.helpers.SCHEDULED_MESSAGE_ID
|
||||||
|
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
|
||||||
|
import com.simplemobiletools.smsmessenger.models.Message
|
||||||
|
import com.simplemobiletools.smsmessenger.receivers.ScheduledMessageReceiver
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All things related to scheduled messages are here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun Context.getScheduleSendPendingIntent(message: Message): PendingIntent {
|
||||||
|
val intent = Intent(this, ScheduledMessageReceiver::class.java)
|
||||||
|
intent.putExtra(THREAD_ID, message.threadId)
|
||||||
|
intent.putExtra(SCHEDULED_MESSAGE_ID, message.id)
|
||||||
|
|
||||||
|
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
return PendingIntent.getBroadcast(this, message.id.toInt(), intent, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.scheduleMessage(message: Message) {
|
||||||
|
val pendingIntent = getScheduleSendPendingIntent(message)
|
||||||
|
val triggerAtMillis = message.millis()
|
||||||
|
|
||||||
|
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
|
||||||
|
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.cancelScheduleSendPendingIntent(messageId: Long) {
|
||||||
|
val intent = Intent(this, ScheduledMessageReceiver::class.java)
|
||||||
|
val flags = PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||||
|
PendingIntent.getBroadcast(this, messageId.toInt(), intent, flags).cancel()
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.simplemobiletools.smsmessenger.messaging
|
||||||
|
|
||||||
|
class SmsException(val errorCode: Int, val exception: Exception? = null) : Exception() {
|
||||||
|
companion object {
|
||||||
|
const val EMPTY_DESTINATION_ADDRESS = -1
|
||||||
|
const val ERROR_PERSISTING_MESSAGE = -2
|
||||||
|
const val ERROR_SENDING_MESSAGE = -3
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.simplemobiletools.smsmessenger.messaging
|
||||||
|
|
||||||
|
import android.telephony.SmsManager
|
||||||
|
import com.klinker.android.send_message.Settings
|
||||||
|
|
||||||
|
private var smsManagerInstance: SmsManager? = null
|
||||||
|
private var associatedSubId: Int = -1
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
fun getSmsManager(subId: Int): SmsManager {
|
||||||
|
if (smsManagerInstance == null || subId != associatedSubId) {
|
||||||
|
smsManagerInstance = if (subId != Settings.DEFAULT_SUBSCRIPTION_ID) {
|
||||||
|
try {
|
||||||
|
smsManagerInstance = SmsManager.getSmsManagerForSubscriptionId(subId)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
smsManagerInstance ?: SmsManager.getDefault()
|
||||||
|
} else {
|
||||||
|
SmsManager.getDefault()
|
||||||
|
}
|
||||||
|
associatedSubId = subId
|
||||||
|
}
|
||||||
|
return smsManagerInstance!!
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
package com.simplemobiletools.smsmessenger.messaging
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
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.ERROR_SENDING_MESSAGE
|
||||||
|
import com.simplemobiletools.smsmessenger.receivers.SendStatusReceiver
|
||||||
|
import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver
|
||||||
|
import com.simplemobiletools.smsmessenger.receivers.SmsStatusSentReceiver
|
||||||
|
|
||||||
|
/** Class that sends chat message via SMS. */
|
||||||
|
class SmsSender(val app: Application) {
|
||||||
|
|
||||||
|
// not sure what to do about this yet. this is the default as per android-smsmms
|
||||||
|
private val sendMultipartSmsAsSeparateMessages = false
|
||||||
|
|
||||||
|
// This should be called from a RequestWriter queue thread
|
||||||
|
fun sendMessage(
|
||||||
|
subId: Int, destination: String, body: String, serviceCenter: String?,
|
||||||
|
requireDeliveryReport: Boolean, messageUri: Uri
|
||||||
|
) {
|
||||||
|
var dest = destination
|
||||||
|
if (body.isEmpty()) {
|
||||||
|
throw IllegalArgumentException("SmsSender: empty text message")
|
||||||
|
}
|
||||||
|
// remove spaces and dashes from destination number
|
||||||
|
// (e.g. "801 555 1212" -> "8015551212")
|
||||||
|
// (e.g. "+8211-123-4567" -> "+82111234567")
|
||||||
|
dest = PhoneNumberUtils.stripSeparators(dest)
|
||||||
|
|
||||||
|
if (dest.isEmpty()) {
|
||||||
|
throw SmsException(EMPTY_DESTINATION_ADDRESS)
|
||||||
|
}
|
||||||
|
// Divide the input message by SMS length limit
|
||||||
|
val smsManager = getSmsManager(subId)
|
||||||
|
val messages = smsManager.divideMessage(body)
|
||||||
|
if (messages == null || messages.size < 1) {
|
||||||
|
throw SmsException(ERROR_SENDING_MESSAGE)
|
||||||
|
}
|
||||||
|
// Actually send the sms
|
||||||
|
sendInternal(
|
||||||
|
subId, dest, messages, serviceCenter, requireDeliveryReport, messageUri
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually sending the message using SmsManager
|
||||||
|
private fun sendInternal(
|
||||||
|
subId: Int, dest: String,
|
||||||
|
messages: ArrayList<String>, serviceCenter: String?,
|
||||||
|
requireDeliveryReport: Boolean, messageUri: Uri
|
||||||
|
) {
|
||||||
|
val smsManager = getSmsManager(subId)
|
||||||
|
val messageCount = messages.size
|
||||||
|
val deliveryIntents = ArrayList<PendingIntent?>(messageCount)
|
||||||
|
val sentIntents = ArrayList<PendingIntent>(messageCount)
|
||||||
|
|
||||||
|
var flags = PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
|
if (isSPlus()) {
|
||||||
|
flags = flags or PendingIntent.FLAG_MUTABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 0 until messageCount) {
|
||||||
|
// Make pending intents different for each message part
|
||||||
|
val partId = if (messageCount <= 1) 0 else i + 1
|
||||||
|
if (requireDeliveryReport && i == messageCount - 1) {
|
||||||
|
deliveryIntents.add(
|
||||||
|
PendingIntent.getBroadcast(
|
||||||
|
app,
|
||||||
|
partId,
|
||||||
|
getDeliveredStatusIntent(messageUri, subId),
|
||||||
|
flags
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
deliveryIntents.add(null)
|
||||||
|
}
|
||||||
|
sentIntents.add(
|
||||||
|
PendingIntent.getBroadcast(
|
||||||
|
app,
|
||||||
|
partId,
|
||||||
|
getSendStatusIntent(messageUri, subId),
|
||||||
|
flags
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (sendMultipartSmsAsSeparateMessages) {
|
||||||
|
// If multipart sms is not supported, send them as separate messages
|
||||||
|
for (i in 0 until messageCount) {
|
||||||
|
smsManager.sendTextMessage(
|
||||||
|
dest,
|
||||||
|
serviceCenter,
|
||||||
|
messages[i],
|
||||||
|
sentIntents[i],
|
||||||
|
deliveryIntents[i]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
smsManager.sendMultipartTextMessage(
|
||||||
|
dest, serviceCenter, messages, sentIntents, deliveryIntents
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw SmsException(ERROR_SENDING_MESSAGE, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSendStatusIntent(requestUri: Uri, subId: Int): Intent {
|
||||||
|
val intent = Intent(SendStatusReceiver.SMS_SENT_ACTION, requestUri, app, SmsStatusSentReceiver::class.java)
|
||||||
|
intent.putExtra(SendStatusReceiver.EXTRA_SUB_ID, subId)
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDeliveredStatusIntent(requestUri: Uri, subId: Int): Intent {
|
||||||
|
val intent = Intent(SendStatusReceiver.SMS_DELIVERED_ACTION, requestUri, app, SmsStatusDeliveredReceiver::class.java)
|
||||||
|
intent.putExtra(SendStatusReceiver.EXTRA_SUB_ID, subId)
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var instance: SmsSender? = null
|
||||||
|
fun getInstance(app: Application): SmsSender {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = SmsSender(app)
|
||||||
|
}
|
||||||
|
return instance!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@ import com.simplemobiletools.smsmessenger.extensions.*
|
|||||||
import com.simplemobiletools.smsmessenger.helpers.REPLY
|
import com.simplemobiletools.smsmessenger.helpers.REPLY
|
||||||
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
|
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
|
||||||
import com.simplemobiletools.smsmessenger.helpers.THREAD_NUMBER
|
import com.simplemobiletools.smsmessenger.helpers.THREAD_NUMBER
|
||||||
import com.simplemobiletools.smsmessenger.helpers.sendMessage
|
import com.simplemobiletools.smsmessenger.messaging.sendMessageCompat
|
||||||
|
|
||||||
class DirectReplyReceiver : BroadcastReceiver() {
|
class DirectReplyReceiver : BroadcastReceiver() {
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
@ -38,7 +38,7 @@ class DirectReplyReceiver : BroadcastReceiver() {
|
|||||||
|
|
||||||
ensureBackgroundThread {
|
ensureBackgroundThread {
|
||||||
try {
|
try {
|
||||||
context.sendMessage(body, listOf(address), subscriptionId, emptyList())
|
context.sendMessageCompat(body, listOf(address), subscriptionId, emptyList())
|
||||||
val message = context.getMessages(threadId, getImageResolutions = false, includeScheduledMessages = false, limit = 1).lastOrNull()
|
val message = context.getMessages(threadId, getImageResolutions = false, includeScheduledMessages = false, limit = 1).lastOrNull()
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
context.messagesDB.insertOrUpdate(message)
|
context.messagesDB.insertOrUpdate(message)
|
||||||
|
@ -1,15 +1,54 @@
|
|||||||
package com.simplemobiletools.smsmessenger.receivers
|
package com.simplemobiletools.smsmessenger.receivers
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.database.sqlite.SQLiteException
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.Telephony
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.simplemobiletools.commons.extensions.showErrorToast
|
||||||
|
import com.simplemobiletools.commons.extensions.toast
|
||||||
|
import com.simplemobiletools.smsmessenger.R
|
||||||
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
|
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class MmsSentReceiver : com.klinker.android.send_message.MmsSentReceiver() {
|
/** Handles updating databases and states when a MMS message is sent. */
|
||||||
override fun onMessageStatusUpdated(context: Context?, intent: Intent?, resultCode: Int) {
|
class MmsSentReceiver : SendStatusReceiver() {
|
||||||
super.onMessageStatusUpdated(context, intent, resultCode)
|
|
||||||
|
override fun updateAndroidDatabase(context: Context, intent: Intent, receiverResultCode: Int) {
|
||||||
|
val uri = Uri.parse(intent.getStringExtra(EXTRA_CONTENT_URI))
|
||||||
|
val messageBox = if (receiverResultCode == Activity.RESULT_OK) {
|
||||||
|
Telephony.Mms.MESSAGE_BOX_SENT
|
||||||
|
} else {
|
||||||
|
val msg = context.getString(R.string.unknown_error_occurred_sending_message, receiverResultCode)
|
||||||
|
context.toast(msg = msg, length = Toast.LENGTH_LONG)
|
||||||
|
Telephony.Mms.MESSAGE_BOX_FAILED
|
||||||
|
}
|
||||||
|
val values = ContentValues(1).apply {
|
||||||
|
put(Telephony.Mms.MESSAGE_BOX, messageBox)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
context.contentResolver.update(uri, values, null, null)
|
||||||
|
} catch (e: SQLiteException) {
|
||||||
|
context.showErrorToast(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
val filePath = intent.getStringExtra(EXTRA_FILE_PATH)
|
||||||
|
if (filePath != null) {
|
||||||
|
File(filePath).delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateAppDatabase(context: Context, intent: Intent, receiverResultCode: Int) {
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
refreshMessages()
|
refreshMessages()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val EXTRA_CONTENT_URI = "content_uri"
|
||||||
|
private const val EXTRA_FILE_PATH = "file_path"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ import com.simplemobiletools.smsmessenger.extensions.messagesDB
|
|||||||
import com.simplemobiletools.smsmessenger.helpers.SCHEDULED_MESSAGE_ID
|
import com.simplemobiletools.smsmessenger.helpers.SCHEDULED_MESSAGE_ID
|
||||||
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
|
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
|
||||||
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
|
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
|
||||||
import com.simplemobiletools.smsmessenger.helpers.sendMessage
|
import com.simplemobiletools.smsmessenger.messaging.sendMessageCompat
|
||||||
|
|
||||||
class ScheduledMessageReceiver : BroadcastReceiver() {
|
class ScheduledMessageReceiver : BroadcastReceiver() {
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ class ScheduledMessageReceiver : BroadcastReceiver() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
context.sendMessage(message.body, addresses, message.subscriptionId, attachments)
|
context.sendMessageCompat(message.body, addresses, message.subscriptionId, attachments)
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete temporary conversation and message as it's already persisted to the telephony db now
|
// delete temporary conversation and message as it's already persisted to the telephony db now
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.simplemobiletools.smsmessenger.receivers
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||||
|
|
||||||
|
abstract class SendStatusReceiver : BroadcastReceiver() {
|
||||||
|
// Updates the status of the message in the internal database
|
||||||
|
abstract fun updateAndroidDatabase(context: Context, intent: Intent, receiverResultCode: Int)
|
||||||
|
|
||||||
|
// allows the implementer to update the status of the message in their database
|
||||||
|
abstract fun updateAppDatabase(context: Context, intent: Intent, receiverResultCode: Int)
|
||||||
|
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val resultCode = resultCode
|
||||||
|
ensureBackgroundThread {
|
||||||
|
updateAndroidDatabase(context, intent, resultCode)
|
||||||
|
updateAppDatabase(context, intent, resultCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SMS_SENT_ACTION = "com.simplemobiletools.smsmessenger.receiver.SMS_SENT"
|
||||||
|
const val SMS_DELIVERED_ACTION = "com.simplemobiletools.smsmessenger.receiver.SMS_DELIVERED"
|
||||||
|
|
||||||
|
// Defined by platform, but no constant provided. See docs for SmsManager.sendTextMessage.
|
||||||
|
const val EXTRA_ERROR_CODE = "errorCode"
|
||||||
|
const val EXTRA_SUB_ID = "subId"
|
||||||
|
|
||||||
|
const val NO_ERROR_CODE = -1
|
||||||
|
}
|
||||||
|
}
|
@ -1,35 +1,95 @@
|
|||||||
package com.simplemobiletools.smsmessenger.receivers
|
package com.simplemobiletools.smsmessenger.receivers
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
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.provider.Telephony.Sms
|
||||||
import android.os.Looper
|
|
||||||
import android.provider.Telephony
|
|
||||||
import com.klinker.android.send_message.DeliveredReceiver
|
|
||||||
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.updateMessageStatus
|
import com.simplemobiletools.smsmessenger.extensions.messagingUtils
|
||||||
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
|
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
|
||||||
|
|
||||||
class SmsStatusDeliveredReceiver : DeliveredReceiver() {
|
/** Handles updating databases and states when a sent SMS message is delivered. */
|
||||||
|
class SmsStatusDeliveredReceiver : SendStatusReceiver() {
|
||||||
|
|
||||||
override fun onMessageStatusUpdated(context: Context, intent: Intent, receiverResultCode: Int) {
|
private var status: Int = Sms.Sent.STATUS_NONE
|
||||||
if (intent.extras?.containsKey("message_uri") == true) {
|
|
||||||
val uri = Uri.parse(intent.getStringExtra("message_uri"))
|
override fun updateAndroidDatabase(context: Context, intent: Intent, receiverResultCode: Int) {
|
||||||
val messageId = uri?.lastPathSegment?.toLong() ?: 0L
|
val messageUri: Uri? = intent.data
|
||||||
ensureBackgroundThread {
|
val smsMessage = context.messagingUtils.getSmsMessageFromDeliveryReport(intent) ?: return
|
||||||
val status = Telephony.Sms.STATUS_COMPLETE
|
|
||||||
context.updateMessageStatus(messageId, status)
|
try {
|
||||||
val updated = context.messagesDB.updateStatus(messageId, status)
|
val format = intent.getStringExtra("format")
|
||||||
if (updated == 0) {
|
status = smsMessage.status
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
// Simple matching up CDMA status with GSM status.
|
||||||
ensureBackgroundThread {
|
if ("3gpp2" == format) {
|
||||||
context.messagesDB.updateStatus(messageId, status)
|
val errorClass = status shr 24 and 0x03
|
||||||
|
val statusCode = status shr 16 and 0x3f
|
||||||
|
status = when (errorClass) {
|
||||||
|
0 -> {
|
||||||
|
if (statusCode == 0x02 /*STATUS_DELIVERED*/) {
|
||||||
|
Sms.STATUS_COMPLETE
|
||||||
|
} else {
|
||||||
|
Sms.STATUS_PENDING
|
||||||
}
|
}
|
||||||
}, 2000)
|
}
|
||||||
|
2 -> {
|
||||||
|
// TODO: Need to check whether SC still trying to deliver the SMS to destination and will send the report again?
|
||||||
|
Sms.STATUS_PENDING
|
||||||
|
}
|
||||||
|
3 -> {
|
||||||
|
Sms.STATUS_FAILED
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Sms.STATUS_PENDING
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} catch (e: NullPointerException) {
|
||||||
|
// Sometimes, SmsMessage.mWrappedSmsMessage is null causing NPE when we access
|
||||||
|
// the methods on it although the SmsMessage itself is not null.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSmsStatusAndDateSent(context, messageUri, System.currentTimeMillis())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSmsStatusAndDateSent(context: Context, messageUri: Uri?, timeSentInMillis: Long = -1L) {
|
||||||
|
val resolver = context.contentResolver
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
if (status != Sms.Sent.STATUS_NONE) {
|
||||||
|
put(Sms.Sent.STATUS, status)
|
||||||
|
}
|
||||||
|
put(Sms.Sent.DATE_SENT, timeSentInMillis)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateAppDatabase(context: Context, intent: Intent, receiverResultCode: Int) {
|
||||||
|
val messageUri: Uri? = intent.data
|
||||||
|
if (messageUri != null) {
|
||||||
|
val messageId = messageUri.lastPathSegment?.toLong() ?: 0L
|
||||||
|
ensureBackgroundThread {
|
||||||
|
if (status != Sms.Sent.STATUS_NONE) {
|
||||||
|
context.messagesDB.updateStatus(messageId, status)
|
||||||
|
}
|
||||||
refreshMessages()
|
refreshMessages()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,39 +6,47 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.provider.Telephony
|
import android.provider.Telephony.Sms
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import com.klinker.android.send_message.SentReceiver
|
|
||||||
import com.simplemobiletools.commons.extensions.getMyContactsCursor
|
import com.simplemobiletools.commons.extensions.getMyContactsCursor
|
||||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||||
import com.simplemobiletools.smsmessenger.extensions.*
|
import com.simplemobiletools.smsmessenger.extensions.*
|
||||||
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
|
import com.simplemobiletools.smsmessenger.helpers.refreshMessages
|
||||||
|
|
||||||
class SmsStatusSentReceiver : SentReceiver() {
|
/** Handles updating databases and states when a SMS message is sent. */
|
||||||
|
class SmsStatusSentReceiver : SendStatusReceiver() {
|
||||||
|
|
||||||
override fun onMessageStatusUpdated(context: Context, intent: Intent, receiverResultCode: Int) {
|
override fun updateAndroidDatabase(context: Context, intent: Intent, receiverResultCode: Int) {
|
||||||
if (intent.extras?.containsKey("message_uri") == true) {
|
val messageUri: Uri? = intent.data
|
||||||
val uri = Uri.parse(intent.getStringExtra("message_uri"))
|
val resultCode = resultCode
|
||||||
val messageId = uri?.lastPathSegment?.toLong() ?: 0L
|
val messagingUtils = context.messagingUtils
|
||||||
|
|
||||||
|
val type = if (resultCode == Activity.RESULT_OK) {
|
||||||
|
Sms.MESSAGE_TYPE_SENT
|
||||||
|
} else {
|
||||||
|
Sms.MESSAGE_TYPE_FAILED
|
||||||
|
}
|
||||||
|
messagingUtils.updateSmsMessageSendingStatus(messageUri, type)
|
||||||
|
messagingUtils.maybeShowErrorToast(
|
||||||
|
resultCode = resultCode,
|
||||||
|
errorCode = intent.getIntExtra(EXTRA_ERROR_CODE, NO_ERROR_CODE)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateAppDatabase(context: Context, intent: Intent, receiverResultCode: Int) {
|
||||||
|
val messageUri = intent.data
|
||||||
|
if (messageUri != null) {
|
||||||
|
val messageId = messageUri.lastPathSegment?.toLong() ?: 0L
|
||||||
ensureBackgroundThread {
|
ensureBackgroundThread {
|
||||||
val type = if (receiverResultCode == Activity.RESULT_OK) {
|
val type = if (receiverResultCode == Activity.RESULT_OK) {
|
||||||
Telephony.Sms.MESSAGE_TYPE_SENT
|
Sms.MESSAGE_TYPE_SENT
|
||||||
} else {
|
} else {
|
||||||
showSendingFailedNotification(context, messageId)
|
showSendingFailedNotification(context, messageId)
|
||||||
Telephony.Sms.MESSAGE_TYPE_FAILED
|
Sms.MESSAGE_TYPE_FAILED
|
||||||
}
|
|
||||||
|
|
||||||
context.updateMessageType(messageId, type)
|
|
||||||
val updated = context.messagesDB.updateType(messageId, type)
|
|
||||||
if (updated == 0) {
|
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
|
||||||
ensureBackgroundThread {
|
|
||||||
context.messagesDB.updateType(messageId, type)
|
|
||||||
}
|
|
||||||
}, 2000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.messagesDB.updateType(messageId, type)
|
||||||
refreshMessages()
|
refreshMessages()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,8 @@ package com.simplemobiletools.smsmessenger.services
|
|||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.klinker.android.send_message.Transaction
|
import com.klinker.android.send_message.Settings
|
||||||
import com.simplemobiletools.smsmessenger.helpers.getSendMessageSettings
|
import com.simplemobiletools.smsmessenger.messaging.sendMessageCompat
|
||||||
import com.simplemobiletools.smsmessenger.receivers.SmsStatusDeliveredReceiver
|
|
||||||
import com.simplemobiletools.smsmessenger.receivers.SmsStatusSentReceiver
|
|
||||||
|
|
||||||
class HeadlessSmsSendService : Service() {
|
class HeadlessSmsSendService : Service() {
|
||||||
override fun onBind(intent: Intent?) = null
|
override fun onBind(intent: Intent?) = null
|
||||||
@ -19,17 +17,11 @@ class HeadlessSmsSendService : Service() {
|
|||||||
|
|
||||||
val number = Uri.decode(intent.dataString!!.removePrefix("sms:").removePrefix("smsto:").removePrefix("mms").removePrefix("mmsto:").trim())
|
val number = Uri.decode(intent.dataString!!.removePrefix("sms:").removePrefix("smsto:").removePrefix("mms").removePrefix("mmsto:").trim())
|
||||||
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
|
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
val settings = getSendMessageSettings()
|
if (!text.isNullOrEmpty()) {
|
||||||
val transaction = Transaction(this, settings)
|
val addresses = listOf(number)
|
||||||
val message = com.klinker.android.send_message.Message(text, number)
|
val subId = Settings.DEFAULT_SUBSCRIPTION_ID
|
||||||
|
sendMessageCompat(text, addresses, subId, emptyList())
|
||||||
val smsSentIntent = Intent(this, SmsStatusSentReceiver::class.java)
|
}
|
||||||
val deliveredIntent = Intent(this, SmsStatusDeliveredReceiver::class.java)
|
|
||||||
|
|
||||||
transaction.setExplicitBroadcastForSentSms(smsSentIntent)
|
|
||||||
transaction.setExplicitBroadcastForDeliveredSms(deliveredIntent)
|
|
||||||
|
|
||||||
transaction.sendNewMessage(message)
|
|
||||||
} catch (ignored: Exception) {
|
} catch (ignored: Exception) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@
|
|||||||
android:id="@+id/thread_messages_fastscroller"
|
android:id="@+id/thread_messages_fastscroller"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintBottom_toTopOf="@id/thread_send_message_holder"
|
app:layout_constraintBottom_toTopOf="@id/reply_disabled_info_holder"
|
||||||
app:layout_constraintTop_toBottomOf="@id/thread_add_contacts"
|
app:layout_constraintTop_toBottomOf="@id/thread_add_contacts"
|
||||||
app:supportSwipeToRefresh="true">
|
app:supportSwipeToRefresh="true">
|
||||||
|
|
||||||
@ -112,7 +112,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:overScrollMode="ifContentScrolls"
|
android:overScrollMode="ifContentScrolls"
|
||||||
android:paddingBottom="@dimen/small_margin"
|
android:paddingBottom="@dimen/medium_margin"
|
||||||
android:scrollbars="none"
|
android:scrollbars="none"
|
||||||
app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"
|
app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"
|
||||||
app:stackFromEnd="true"
|
app:stackFromEnd="true"
|
||||||
@ -121,215 +121,24 @@
|
|||||||
|
|
||||||
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<include
|
||||||
android:id="@+id/thread_send_message_holder"
|
layout="@layout/layout_invalid_short_code_info"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/thread_send_message_holder"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/thread_messages_fastscroller"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
layout="@layout/layout_thread_send_message_holder"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent">
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/scheduled_message_holder"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/normal_margin"
|
|
||||||
android:layout_marginTop="@dimen/medium_margin"
|
|
||||||
android:layout_marginEnd="@dimen/medium_margin"
|
|
||||||
android:background="@drawable/section_holder_stroke"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/thread_attachments_recyclerview"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_goneMarginBottom="@dimen/medium_margin"
|
|
||||||
tools:visibility="visible">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/scheduled_message_icon"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignTop="@+id/scheduled_message_button"
|
|
||||||
android:layout_alignBottom="@+id/scheduled_message_button"
|
|
||||||
android:paddingTop="@dimen/medium_margin"
|
|
||||||
android:paddingBottom="@dimen/medium_margin"
|
|
||||||
android:src="@drawable/ic_clock_vector" />
|
|
||||||
|
|
||||||
<com.simplemobiletools.commons.views.MyTextView
|
|
||||||
android:id="@+id/scheduled_message_button"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="start"
|
|
||||||
android:layout_toStartOf="@+id/discard_scheduled_message"
|
|
||||||
android:layout_toEndOf="@+id/scheduled_message_icon"
|
|
||||||
android:background="@drawable/ripple_background"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:minHeight="@dimen/normal_icon_size"
|
|
||||||
android:paddingStart="@dimen/small_margin"
|
|
||||||
android:paddingEnd="@dimen/activity_margin"
|
|
||||||
android:textSize="@dimen/middle_text_size"
|
|
||||||
tools:ignore="HardcodedText"
|
|
||||||
tools:text="Tomorrow at 6PM GMT +05:30" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/discard_scheduled_message"
|
|
||||||
android:layout_width="@dimen/normal_icon_size"
|
|
||||||
android:layout_height="@dimen/normal_icon_size"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:background="?selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/cancel_schedule_send"
|
|
||||||
android:padding="@dimen/normal_margin"
|
|
||||||
android:src="@drawable/ic_cross_vector" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/thread_attachments_recyclerview"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/tiny_margin"
|
|
||||||
android:layout_marginBottom="@dimen/small_margin"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingStart="@dimen/normal_margin"
|
|
||||||
android:paddingEnd="@dimen/normal_margin"
|
|
||||||
android:scrollbars="none"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/thread_type_message"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_goneMarginTop="@dimen/medium_margin"
|
|
||||||
tools:itemCount="2"
|
|
||||||
tools:listitem="@layout/item_attachment_document_preview"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/thread_add_attachment"
|
|
||||||
android:layout_width="@dimen/normal_icon_size"
|
|
||||||
android:layout_height="@dimen/normal_icon_size"
|
|
||||||
android:layout_marginStart="@dimen/small_margin"
|
|
||||||
android:alpha="0.9"
|
|
||||||
android:background="?selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/attachment"
|
|
||||||
android:padding="@dimen/normal_margin"
|
|
||||||
android:src="@drawable/ic_plus_vector"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/thread_type_message"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/thread_type_message" />
|
|
||||||
|
|
||||||
<com.simplemobiletools.commons.views.MyEditText
|
|
||||||
android:id="@+id/thread_type_message"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/small_margin"
|
|
||||||
android:layout_marginEnd="@dimen/small_margin"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:hint="@string/type_a_message"
|
|
||||||
android:inputType="textCapSentences|textMultiLine"
|
|
||||||
android:minHeight="@dimen/normal_icon_size"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/attachment_picker_divider"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/thread_select_sim_icon"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/thread_add_attachment" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/thread_select_sim_icon"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:alpha="0.9"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:paddingStart="@dimen/medium_margin"
|
|
||||||
android:paddingTop="@dimen/normal_margin"
|
|
||||||
android:paddingEnd="@dimen/medium_margin"
|
|
||||||
android:paddingBottom="@dimen/normal_margin"
|
|
||||||
android:src="@drawable/ic_sim_vector"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/thread_type_message"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/thread_character_counter"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/thread_type_message"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/thread_select_sim_number"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:textSize="@dimen/normal_text_size"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/thread_select_sim_icon"
|
|
||||||
app:layout_constraintEnd_toEndOf="@id/thread_select_sim_icon"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/thread_select_sim_icon"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/thread_select_sim_icon"
|
|
||||||
tools:text="1"
|
|
||||||
tools:textColor="@color/dark_grey"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<com.simplemobiletools.commons.views.MyTextView
|
|
||||||
android:id="@+id/thread_character_counter"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center"
|
|
||||||
android:paddingStart="@dimen/small_margin"
|
|
||||||
android:paddingEnd="@dimen/small_margin"
|
|
||||||
android:text="0"
|
|
||||||
android:textSize="@dimen/normal_text_size"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/thread_type_message"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/thread_send_message"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/thread_type_message"
|
|
||||||
tools:ignore="HardcodedText"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<com.simplemobiletools.commons.views.MyButton
|
|
||||||
android:id="@+id/thread_send_message"
|
|
||||||
android:layout_width="@dimen/normal_icon_size"
|
|
||||||
android:layout_height="@dimen/normal_icon_size"
|
|
||||||
android:layout_marginEnd="@dimen/small_margin"
|
|
||||||
android:alpha="0.4"
|
|
||||||
android:background="?selectableItemBackgroundBorderless"
|
|
||||||
android:clickable="false"
|
|
||||||
android:contentDescription="@string/ok"
|
|
||||||
android:drawableTop="@drawable/ic_send_vector"
|
|
||||||
android:paddingVertical="@dimen/small_margin"
|
|
||||||
android:text="@string/sms"
|
|
||||||
android:textSize="@dimen/smaller_text_size"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/thread_type_message"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/thread_type_message" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/attachment_picker_divider"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/divider_height"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:background="@color/divider_grey"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/attachment_picker_holder" />
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:id="@+id/attachment_picker_holder"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="@dimen/small_margin"
|
|
||||||
android:overScrollMode="always"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
tools:visibility="visible">
|
|
||||||
|
|
||||||
<include
|
|
||||||
layout="@layout/layout_attachment_picker"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
11
app/src/main/res/layout/dialog_invalid_number.xml
Normal file
11
app/src/main/res/layout/dialog_invalid_number.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.simplemobiletools.commons.views.MyTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/dialog_invalid_number_desc"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/big_margin"
|
||||||
|
android:paddingTop="@dimen/big_margin"
|
||||||
|
android:paddingEnd="@dimen/big_margin"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
tools:text="My sample text" />
|
35
app/src/main/res/layout/layout_invalid_short_code_info.xml
Normal file
35
app/src/main/res/layout/layout_invalid_short_code_info.xml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/reply_disabled_info_holder"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingHorizontal="@dimen/normal_margin">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/reply_disabled_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_toEndOf="@+id/reply_disabled_info"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:padding="@dimen/activity_margin"
|
||||||
|
android:text="@string/invalid_short_code" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/reply_disabled_info"
|
||||||
|
android:layout_width="@dimen/normal_icon_size"
|
||||||
|
android:layout_height="@dimen/normal_icon_size"
|
||||||
|
android:layout_alignTop="@id/reply_disabled_text"
|
||||||
|
android:layout_alignBottom="@id/reply_disabled_text"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/more_info"
|
||||||
|
android:padding="@dimen/normal_margin"
|
||||||
|
android:src="@drawable/ic_info_vector"
|
||||||
|
android:tooltipText="@string/more_info" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
209
app/src/main/res/layout/layout_thread_send_message_holder.xml
Normal file
209
app/src/main/res/layout/layout_thread_send_message_holder.xml
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/thread_send_message_holder"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/scheduled_message_holder"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/normal_margin"
|
||||||
|
android:layout_marginTop="@dimen/medium_margin"
|
||||||
|
android:layout_marginEnd="@dimen/medium_margin"
|
||||||
|
android:background="@drawable/section_holder_stroke"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/thread_attachments_recyclerview"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_goneMarginBottom="@dimen/medium_margin"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/scheduled_message_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignTop="@+id/scheduled_message_button"
|
||||||
|
android:layout_alignBottom="@+id/scheduled_message_button"
|
||||||
|
android:paddingTop="@dimen/medium_margin"
|
||||||
|
android:paddingBottom="@dimen/medium_margin"
|
||||||
|
android:src="@drawable/ic_clock_vector" />
|
||||||
|
|
||||||
|
<com.simplemobiletools.commons.views.MyTextView
|
||||||
|
android:id="@+id/scheduled_message_button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:layout_toStartOf="@+id/discard_scheduled_message"
|
||||||
|
android:layout_toEndOf="@+id/scheduled_message_icon"
|
||||||
|
android:background="@drawable/ripple_background"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:minHeight="@dimen/normal_icon_size"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingEnd="@dimen/activity_margin"
|
||||||
|
android:textSize="@dimen/middle_text_size"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:text="Tomorrow at 6PM GMT +05:30" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/discard_scheduled_message"
|
||||||
|
android:layout_width="@dimen/normal_icon_size"
|
||||||
|
android:layout_height="@dimen/normal_icon_size"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/cancel_schedule_send"
|
||||||
|
android:padding="@dimen/normal_margin"
|
||||||
|
android:src="@drawable/ic_cross_vector" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/thread_attachments_recyclerview"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/tiny_margin"
|
||||||
|
android:layout_marginBottom="@dimen/small_margin"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="@dimen/normal_margin"
|
||||||
|
android:paddingEnd="@dimen/normal_margin"
|
||||||
|
android:scrollbars="none"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/thread_type_message"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_goneMarginTop="@dimen/medium_margin"
|
||||||
|
tools:itemCount="2"
|
||||||
|
tools:listitem="@layout/item_attachment_document_preview"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/thread_add_attachment"
|
||||||
|
android:layout_width="@dimen/normal_icon_size"
|
||||||
|
android:layout_height="@dimen/normal_icon_size"
|
||||||
|
android:layout_marginStart="@dimen/small_margin"
|
||||||
|
android:alpha="0.9"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/attachment"
|
||||||
|
android:padding="@dimen/normal_margin"
|
||||||
|
android:src="@drawable/ic_plus_vector"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/thread_type_message"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/thread_type_message" />
|
||||||
|
|
||||||
|
<com.simplemobiletools.commons.views.MyEditText
|
||||||
|
android:id="@+id/thread_type_message"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/small_margin"
|
||||||
|
android:layout_marginEnd="@dimen/small_margin"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:hint="@string/type_a_message"
|
||||||
|
android:inputType="textCapSentences|textMultiLine"
|
||||||
|
android:minHeight="@dimen/normal_icon_size"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/attachment_picker_divider"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/thread_select_sim_icon"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/thread_add_attachment" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/thread_select_sim_icon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:alpha="0.9"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:paddingStart="@dimen/medium_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingEnd="@dimen/medium_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:src="@drawable/ic_sim_vector"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/thread_type_message"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/thread_character_counter"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/thread_type_message"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/thread_select_sim_number"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="@dimen/normal_text_size"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/thread_select_sim_icon"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/thread_select_sim_icon"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/thread_select_sim_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/thread_select_sim_icon"
|
||||||
|
tools:text="1"
|
||||||
|
tools:textColor="@color/dark_grey"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.simplemobiletools.commons.views.MyTextView
|
||||||
|
android:id="@+id/thread_character_counter"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingEnd="@dimen/small_margin"
|
||||||
|
android:text="0"
|
||||||
|
android:textSize="@dimen/normal_text_size"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/thread_type_message"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/thread_send_message"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/thread_type_message"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<com.simplemobiletools.commons.views.MyButton
|
||||||
|
android:id="@+id/thread_send_message"
|
||||||
|
android:layout_width="@dimen/normal_icon_size"
|
||||||
|
android:layout_height="@dimen/normal_icon_size"
|
||||||
|
android:layout_marginEnd="@dimen/small_margin"
|
||||||
|
android:alpha="0.4"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="false"
|
||||||
|
android:contentDescription="@string/ok"
|
||||||
|
android:drawableTop="@drawable/ic_send_vector"
|
||||||
|
android:paddingVertical="@dimen/small_margin"
|
||||||
|
android:text="@string/sms"
|
||||||
|
android:textSize="@dimen/smaller_text_size"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/thread_type_message"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/thread_type_message" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/attachment_picker_divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/divider_height"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:background="@color/divider_grey"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/attachment_picker_holder" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/attachment_picker_holder"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="@dimen/small_margin"
|
||||||
|
android:overScrollMode="always"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:visibility="visible">
|
||||||
|
|
||||||
|
<include
|
||||||
|
layout="@layout/layout_attachment_picker"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
</ScrollView>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Reference in New Issue
Block a user