Improve third party SMS/MMS intent parsing
Closes https://github.com/FossifyOrg/Messages/issues/243 Closes https://github.com/FossifyOrg/Messages/issues/217
This commit is contained in:
parent
f66aa77831
commit
4434d187bc
3 changed files with 152 additions and 17 deletions
|
|
@ -80,7 +80,6 @@
|
||||||
android:parentActivityName=".activities.MainActivity">
|
android:parentActivityName=".activities.MainActivity">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
|
||||||
<action android:name="android.intent.action.SENDTO" />
|
<action android:name="android.intent.action.SENDTO" />
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,31 @@ 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 org.fossify.commons.dialogs.RadioGroupDialog
|
import org.fossify.commons.dialogs.RadioGroupDialog
|
||||||
import org.fossify.commons.extensions.*
|
import org.fossify.commons.extensions.applyColorFilter
|
||||||
import org.fossify.commons.helpers.*
|
import org.fossify.commons.extensions.areSystemAnimationsEnabled
|
||||||
|
import org.fossify.commons.extensions.beGone
|
||||||
|
import org.fossify.commons.extensions.beVisible
|
||||||
|
import org.fossify.commons.extensions.beVisibleIf
|
||||||
|
import org.fossify.commons.extensions.getColorStateList
|
||||||
|
import org.fossify.commons.extensions.getContrastColor
|
||||||
|
import org.fossify.commons.extensions.getMyContactsCursor
|
||||||
|
import org.fossify.commons.extensions.getPhoneNumberTypeText
|
||||||
|
import org.fossify.commons.extensions.getProperPrimaryColor
|
||||||
|
import org.fossify.commons.extensions.getProperTextColor
|
||||||
|
import org.fossify.commons.extensions.hasPermission
|
||||||
|
import org.fossify.commons.extensions.hideKeyboard
|
||||||
|
import org.fossify.commons.extensions.normalizeString
|
||||||
|
import org.fossify.commons.extensions.onTextChangeListener
|
||||||
|
import org.fossify.commons.extensions.toast
|
||||||
|
import org.fossify.commons.extensions.underlineText
|
||||||
|
import org.fossify.commons.extensions.updateTextColors
|
||||||
|
import org.fossify.commons.extensions.value
|
||||||
|
import org.fossify.commons.extensions.viewBinding
|
||||||
|
import org.fossify.commons.helpers.MyContactsContentProvider
|
||||||
|
import org.fossify.commons.helpers.NavigationIcon
|
||||||
|
import org.fossify.commons.helpers.PERMISSION_READ_CONTACTS
|
||||||
|
import org.fossify.commons.helpers.SimpleContactsHelper
|
||||||
|
import org.fossify.commons.helpers.ensureBackgroundThread
|
||||||
import org.fossify.commons.models.RadioItem
|
import org.fossify.commons.models.RadioItem
|
||||||
import org.fossify.commons.models.SimpleContact
|
import org.fossify.commons.models.SimpleContact
|
||||||
import org.fossify.messages.R
|
import org.fossify.messages.R
|
||||||
|
|
@ -18,7 +41,13 @@ import org.fossify.messages.databinding.ActivityNewConversationBinding
|
||||||
import org.fossify.messages.databinding.ItemSuggestedContactBinding
|
import org.fossify.messages.databinding.ItemSuggestedContactBinding
|
||||||
import org.fossify.messages.extensions.getSuggestedContacts
|
import org.fossify.messages.extensions.getSuggestedContacts
|
||||||
import org.fossify.messages.extensions.getThreadId
|
import org.fossify.messages.extensions.getThreadId
|
||||||
import org.fossify.messages.helpers.*
|
import org.fossify.messages.helpers.SmsIntentParser
|
||||||
|
import org.fossify.messages.helpers.THREAD_ATTACHMENT_URI
|
||||||
|
import org.fossify.messages.helpers.THREAD_ATTACHMENT_URIS
|
||||||
|
import org.fossify.messages.helpers.THREAD_ID
|
||||||
|
import org.fossify.messages.helpers.THREAD_NUMBER
|
||||||
|
import org.fossify.messages.helpers.THREAD_TEXT
|
||||||
|
import org.fossify.messages.helpers.THREAD_TITLE
|
||||||
import org.fossify.messages.messaging.isShortCodeWithLetters
|
import org.fossify.messages.messaging.isShortCodeWithLetters
|
||||||
import java.net.URLDecoder
|
import java.net.URLDecoder
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
@ -42,7 +71,10 @@ class NewConversationActivity : SimpleActivity() {
|
||||||
useTransparentNavigation = true,
|
useTransparentNavigation = true,
|
||||||
useTopSearchMenu = false
|
useTopSearchMenu = false
|
||||||
)
|
)
|
||||||
setupMaterialScrollListener(scrollingView = binding.contactsList, toolbar = binding.newConversationToolbar)
|
setupMaterialScrollListener(
|
||||||
|
scrollingView = binding.contactsList,
|
||||||
|
toolbar = binding.newConversationToolbar
|
||||||
|
)
|
||||||
|
|
||||||
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||||
binding.newConversationAddress.requestFocus()
|
binding.newConversationAddress.requestFocus()
|
||||||
|
|
@ -112,9 +144,14 @@ class NewConversationActivity : SimpleActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isThirdPartyIntent(): Boolean {
|
private fun isThirdPartyIntent(): Boolean {
|
||||||
if ((intent.action == Intent.ACTION_SENDTO || intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_VIEW) && intent.dataString != null) {
|
val result = SmsIntentParser.parse(intent)
|
||||||
val number = intent.dataString!!.removePrefix("sms:").removePrefix("smsto:").removePrefix("mms").removePrefix("mmsto:").replace("+", "%2b").trim()
|
if (result != null) {
|
||||||
launchThreadActivity(URLDecoder.decode(number), "")
|
val (body, recipients) = result
|
||||||
|
launchThreadActivity(
|
||||||
|
phoneNumber = URLDecoder.decode(recipients),
|
||||||
|
name = "",
|
||||||
|
body = body
|
||||||
|
)
|
||||||
finish()
|
finish()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +179,11 @@ class NewConversationActivity : SimpleActivity() {
|
||||||
val hasContacts = contacts.isNotEmpty()
|
val hasContacts = contacts.isNotEmpty()
|
||||||
binding.contactsList.beVisibleIf(hasContacts)
|
binding.contactsList.beVisibleIf(hasContacts)
|
||||||
binding.noContactsPlaceholder.beVisibleIf(!hasContacts)
|
binding.noContactsPlaceholder.beVisibleIf(!hasContacts)
|
||||||
binding.noContactsPlaceholder2.beVisibleIf(!hasContacts && !hasPermission(PERMISSION_READ_CONTACTS))
|
binding.noContactsPlaceholder2.beVisibleIf(
|
||||||
|
!hasContacts && !hasPermission(
|
||||||
|
PERMISSION_READ_CONTACTS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if (!hasContacts) {
|
if (!hasContacts) {
|
||||||
val placeholderText = if (hasPermission(PERMISSION_READ_CONTACTS)) {
|
val placeholderText = if (hasPermission(PERMISSION_READ_CONTACTS)) {
|
||||||
|
|
@ -168,7 +209,13 @@ class NewConversationActivity : SimpleActivity() {
|
||||||
val items = ArrayList<RadioItem>()
|
val items = ArrayList<RadioItem>()
|
||||||
phoneNumbers.forEachIndexed { index, phoneNumber ->
|
phoneNumbers.forEachIndexed { index, phoneNumber ->
|
||||||
val type = getPhoneNumberTypeText(phoneNumber.type, phoneNumber.label)
|
val type = getPhoneNumberTypeText(phoneNumber.type, phoneNumber.label)
|
||||||
items.add(RadioItem(index, "${phoneNumber.normalizedNumber} ($type)", phoneNumber.normalizedNumber))
|
items.add(
|
||||||
|
RadioItem(
|
||||||
|
index,
|
||||||
|
"${phoneNumber.normalizedNumber} ($type)",
|
||||||
|
phoneNumber.normalizedNumber
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
RadioGroupDialog(this, items) {
|
RadioGroupDialog(this, items) {
|
||||||
|
|
@ -212,10 +259,17 @@ class NewConversationActivity : SimpleActivity() {
|
||||||
suggestedContactName.setTextColor(getProperTextColor())
|
suggestedContactName.setTextColor(getProperTextColor())
|
||||||
|
|
||||||
if (!isDestroyed) {
|
if (!isDestroyed) {
|
||||||
SimpleContactsHelper(this@NewConversationActivity).loadContactImage(contact.photoUri, suggestedContactImage, contact.name)
|
SimpleContactsHelper(this@NewConversationActivity).loadContactImage(
|
||||||
|
contact.photoUri,
|
||||||
|
suggestedContactImage,
|
||||||
|
contact.name
|
||||||
|
)
|
||||||
binding.suggestionsHolder.addView(root)
|
binding.suggestionsHolder.addView(root)
|
||||||
root.setOnClickListener {
|
root.setOnClickListener {
|
||||||
launchThreadActivity(contact.phoneNumbers.first().normalizedNumber, contact.name)
|
launchThreadActivity(
|
||||||
|
contact.phoneNumbers.first().normalizedNumber,
|
||||||
|
contact.name
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -231,28 +285,32 @@ class NewConversationActivity : SimpleActivity() {
|
||||||
try {
|
try {
|
||||||
val name = contacts[position].name
|
val name = contacts[position].name
|
||||||
val character = if (name.isNotEmpty()) name.substring(0, 1) else ""
|
val character = if (name.isNotEmpty()) name.substring(0, 1) else ""
|
||||||
FastScrollItemIndicator.Text(character.uppercase(Locale.getDefault()).normalizeString())
|
FastScrollItemIndicator.Text(
|
||||||
|
character.uppercase(Locale.getDefault()).normalizeString()
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
FastScrollItemIndicator.Text("")
|
FastScrollItemIndicator.Text("")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchThreadActivity(phoneNumber: String, name: String) {
|
private fun launchThreadActivity(phoneNumber: String, name: String, body: String = "") {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
val text = intent.getStringExtra(Intent.EXTRA_TEXT) ?: intent.getStringExtra("sms_body") ?: ""
|
|
||||||
val numbers = phoneNumber.split(";").toSet()
|
val numbers = phoneNumber.split(";").toSet()
|
||||||
val number = if (numbers.size == 1) phoneNumber else Gson().toJson(numbers)
|
val number = if (numbers.size == 1) phoneNumber else Gson().toJson(numbers)
|
||||||
Intent(this, ThreadActivity::class.java).apply {
|
Intent(this, ThreadActivity::class.java).apply {
|
||||||
putExtra(THREAD_ID, getThreadId(numbers))
|
putExtra(THREAD_ID, getThreadId(numbers))
|
||||||
putExtra(THREAD_TITLE, name)
|
putExtra(THREAD_TITLE, name)
|
||||||
putExtra(THREAD_TEXT, text)
|
putExtra(THREAD_TEXT, body)
|
||||||
putExtra(THREAD_NUMBER, number)
|
putExtra(THREAD_NUMBER, number)
|
||||||
|
|
||||||
if (intent.action == Intent.ACTION_SEND && intent.extras?.containsKey(Intent.EXTRA_STREAM) == true) {
|
if (intent.action == Intent.ACTION_SEND && intent.extras?.containsKey(Intent.EXTRA_STREAM) == true) {
|
||||||
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||||
putExtra(THREAD_ATTACHMENT_URI, uri?.toString())
|
putExtra(THREAD_ATTACHMENT_URI, uri?.toString())
|
||||||
} else if (intent.action == Intent.ACTION_SEND_MULTIPLE && intent.extras?.containsKey(Intent.EXTRA_STREAM) == true) {
|
} else if (intent.action == Intent.ACTION_SEND_MULTIPLE && intent.extras?.containsKey(
|
||||||
|
Intent.EXTRA_STREAM
|
||||||
|
) == true
|
||||||
|
) {
|
||||||
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
|
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
|
||||||
putExtra(THREAD_ATTACHMENT_URIS, uris)
|
putExtra(THREAD_ATTACHMENT_URIS, uris)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
package org.fossify.messages.helpers
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import com.google.android.mms.ContentType
|
||||||
|
import java.io.UnsupportedEncodingException
|
||||||
|
import java.net.URLDecoder
|
||||||
|
|
||||||
|
// Base on https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Messaging/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java
|
||||||
|
object SmsIntentParser {
|
||||||
|
private const val SCHEME_SMS = "sms"
|
||||||
|
private const val SCHEME_SMSTO = "smsto"
|
||||||
|
private const val SCHEME_MMS = "mms"
|
||||||
|
private const val SCHEME_MMSTO = "mmsto"
|
||||||
|
private val SMS_MMS_SCHEMES = setOf(SCHEME_SMS, SCHEME_SMSTO, SCHEME_MMS, SCHEME_MMSTO)
|
||||||
|
|
||||||
|
private const val MAX_RECIPIENT_LENGTH = 100
|
||||||
|
private const val SMS_BODY = "sms_body"
|
||||||
|
private const val ADDRESS = "address"
|
||||||
|
|
||||||
|
fun parse(intent: Intent): Pair<String, String>? {
|
||||||
|
val action = intent.action
|
||||||
|
if (action != Intent.ACTION_SENDTO && action != Intent.ACTION_VIEW) {
|
||||||
|
// Unsupported intent action
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val recipients = parseRecipients(intent)
|
||||||
|
val body = extractBodyFromIntent(intent)
|
||||||
|
return body.orEmpty() to recipients
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseRecipients(intent: Intent): String {
|
||||||
|
val uriRecipients = parseRecipientsFromUri(intent.data)
|
||||||
|
val extraAddress = intent.getStringExtra(ADDRESS)
|
||||||
|
val extraEmail = intent.getStringExtra(Intent.EXTRA_EMAIL)
|
||||||
|
|
||||||
|
val recipients = when {
|
||||||
|
!extraAddress.isNullOrEmpty() -> arrayOf(extraAddress)
|
||||||
|
!extraEmail.isNullOrEmpty() -> arrayOf(extraEmail)
|
||||||
|
else -> uriRecipients.orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipients
|
||||||
|
.filter { it.length < MAX_RECIPIENT_LENGTH }
|
||||||
|
.joinToString(";")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseRecipientsFromUri(uri: Uri?): Array<String>? {
|
||||||
|
if (uri == null || uri.scheme !in SMS_MMS_SCHEMES) return null
|
||||||
|
val schemeSpecificPart = uri.schemeSpecificPart.split("?").firstOrNull() ?: return null
|
||||||
|
return schemeSpecificPart.replace(';', ',').split(",").toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractBodyFromIntent(intent: Intent): String? {
|
||||||
|
val uriBody = extractBodyFromUri(intent.data)
|
||||||
|
val smsBody = intent.getStringExtra(SMS_BODY)
|
||||||
|
val extraText = if (ContentType.TEXT_PLAIN == intent.type) {
|
||||||
|
intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
|
} else {
|
||||||
|
// Invalid URL, probably
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
return smsBody ?: uriBody ?: extraText
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractBodyFromUri(uri: Uri?): String? {
|
||||||
|
if (uri == null) return null
|
||||||
|
val query = uri.query ?: return null
|
||||||
|
val bodyParam = query.split("&").firstOrNull { it.startsWith("body=") } ?: return null
|
||||||
|
return try {
|
||||||
|
URLDecoder.decode(bodyParam.removePrefix("body="), "UTF-8")
|
||||||
|
} catch (e: UnsupportedEncodingException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue