Remove support for Android and older versions

See https://github.com/orgs/FossifyOrg/discussions/241
This commit is contained in:
Naveen Singh 2024-12-26 14:00:24 +05:30
parent 3f17c2d94c
commit 7d2b87adc1
No known key found for this signature in database
GPG key ID: AF5D43C216778C0B
108 changed files with 347 additions and 137 deletions

View file

@ -76,7 +76,7 @@ android {
} }
compileOptions { compileOptions {
val currentJavaVersionFromLibs = JavaVersion.valueOf(libs.versions.app.build.javaVersion.get().toString()) val currentJavaVersionFromLibs = JavaVersion.valueOf(libs.versions.app.build.javaVersion.get())
sourceCompatibility = currentJavaVersionFromLibs sourceCompatibility = currentJavaVersionFromLibs
targetCompatibility = currentJavaVersionFromLibs targetCompatibility = currentJavaVersionFromLibs
} }

View file

@ -6,20 +6,31 @@ import android.content.Intent
import android.media.AudioAttributes import android.media.AudioAttributes
import android.media.AudioManager import android.media.AudioManager
import android.media.RingtoneManager import android.media.RingtoneManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.Settings import android.provider.Settings
import androidx.annotation.RequiresApi
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import org.fossify.commons.extensions.* import org.fossify.commons.extensions.applyColorFilter
import org.fossify.commons.extensions.beGone
import org.fossify.commons.extensions.beVisible
import org.fossify.commons.extensions.beVisibleIf
import org.fossify.commons.extensions.getProperPrimaryColor
import org.fossify.commons.extensions.getProperTextColor
import org.fossify.commons.extensions.notificationManager
import org.fossify.commons.extensions.updateTextColors
import org.fossify.commons.extensions.viewBinding
import org.fossify.commons.helpers.NavigationIcon import org.fossify.commons.helpers.NavigationIcon
import org.fossify.commons.helpers.ensureBackgroundThread import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.commons.helpers.isOreoPlus
import org.fossify.commons.models.SimpleContact import org.fossify.commons.models.SimpleContact
import org.fossify.messages.adapters.ContactsAdapter import org.fossify.messages.adapters.ContactsAdapter
import org.fossify.messages.databinding.ActivityConversationDetailsBinding import org.fossify.messages.databinding.ActivityConversationDetailsBinding
import org.fossify.messages.dialogs.RenameConversationDialog import org.fossify.messages.dialogs.RenameConversationDialog
import org.fossify.messages.extensions.* import org.fossify.messages.extensions.config
import org.fossify.messages.extensions.conversationsDB
import org.fossify.messages.extensions.getContactFromAddress
import org.fossify.messages.extensions.getThreadParticipants
import org.fossify.messages.extensions.messagesDB
import org.fossify.messages.extensions.renameConversation
import org.fossify.messages.extensions.startContactDetailsIntent
import org.fossify.messages.helpers.THREAD_ID import org.fossify.messages.helpers.THREAD_ID
import org.fossify.messages.models.Conversation import org.fossify.messages.models.Conversation
@ -42,7 +53,10 @@ class ConversationDetailsActivity : SimpleActivity() {
useTransparentNavigation = true, useTransparentNavigation = true,
useTopSearchMenu = false useTopSearchMenu = false
) )
setupMaterialScrollListener(scrollingView = binding.participantsRecyclerview, toolbar = binding.conversationDetailsToolbar) setupMaterialScrollListener(
scrollingView = binding.participantsRecyclerview,
toolbar = binding.conversationDetailsToolbar
)
threadId = intent.getLongExtra(THREAD_ID, 0L) threadId = intent.getLongExtra(THREAD_ID, 0L)
ensureBackgroundThread { ensureBackgroundThread {
@ -56,12 +70,10 @@ class ConversationDetailsActivity : SimpleActivity() {
runOnUiThread { runOnUiThread {
setupTextViews() setupTextViews()
setupParticipants() setupParticipants()
if (isOreoPlus()) {
setupCustomNotifications() setupCustomNotifications()
} }
} }
} }
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@ -73,7 +85,6 @@ class ConversationDetailsActivity : SimpleActivity() {
binding.membersHeading.setTextColor(primaryColor) binding.membersHeading.setTextColor(primaryColor)
} }
@RequiresApi(Build.VERSION_CODES.O)
private fun setupCustomNotifications() { private fun setupCustomNotifications() {
binding.apply { binding.apply {
notificationsHeading.beVisible() notificationsHeading.beVisible()
@ -104,7 +115,6 @@ class ConversationDetailsActivity : SimpleActivity() {
} }
} }
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel() { private fun createNotificationChannel() {
val name = conversation?.title val name = conversation?.title
val audioAttributes = AudioAttributes.Builder() val audioAttributes = AudioAttributes.Builder()
@ -116,27 +126,36 @@ class ConversationDetailsActivity : SimpleActivity() {
NotificationChannel(threadId.toString(), name, NotificationManager.IMPORTANCE_HIGH).apply { NotificationChannel(threadId.toString(), name, NotificationManager.IMPORTANCE_HIGH).apply {
setBypassDnd(false) setBypassDnd(false)
enableLights(true) enableLights(true)
setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), audioAttributes) setSound(
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
audioAttributes
)
enableVibration(true) enableVibration(true)
notificationManager.createNotificationChannel(this) notificationManager.createNotificationChannel(this)
} }
} }
@RequiresApi(Build.VERSION_CODES.O)
private fun removeNotificationChannel() { private fun removeNotificationChannel() {
notificationManager.deleteNotificationChannel(threadId.toString()) notificationManager.deleteNotificationChannel(threadId.toString())
} }
private fun setupTextViews() { private fun setupTextViews() {
binding.conversationName.apply { binding.conversationName.apply {
ResourcesCompat.getDrawable(resources, org.fossify.commons.R.drawable.ic_edit_vector, theme)?.apply { ResourcesCompat.getDrawable(
resources,
org.fossify.commons.R.drawable.ic_edit_vector,
theme
)?.apply {
applyColorFilter(getProperTextColor()) applyColorFilter(getProperTextColor())
setCompoundDrawablesWithIntrinsicBounds(null, null, this, null) setCompoundDrawablesWithIntrinsicBounds(null, null, this, null)
} }
text = conversation?.title text = conversation?.title
setOnClickListener { setOnClickListener {
RenameConversationDialog(this@ConversationDetailsActivity, conversation!!) { title -> RenameConversationDialog(
this@ConversationDetailsActivity,
conversation!!
) { title ->
text = title text = title
ensureBackgroundThread { ensureBackgroundThread {
conversation = renameConversation(conversation!!, newTitle = title) conversation = renameConversation(conversation!!, newTitle = title)

View file

@ -49,7 +49,6 @@ import org.fossify.commons.helpers.PERMISSION_READ_SMS
import org.fossify.commons.helpers.PERMISSION_SEND_SMS import org.fossify.commons.helpers.PERMISSION_SEND_SMS
import org.fossify.commons.helpers.SHORT_ANIMATION_DURATION import org.fossify.commons.helpers.SHORT_ANIMATION_DURATION
import org.fossify.commons.helpers.ensureBackgroundThread import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.commons.helpers.isNougatMR1Plus
import org.fossify.commons.helpers.isQPlus import org.fossify.commons.helpers.isQPlus
import org.fossify.commons.models.FAQItem import org.fossify.commons.models.FAQItem
import org.fossify.commons.models.Release import org.fossify.commons.models.Release
@ -494,7 +493,7 @@ class MainActivity : SimpleActivity() {
@SuppressLint("NewApi") @SuppressLint("NewApi")
private fun checkShortcut() { private fun checkShortcut() {
val appIconColor = config.appIconColor val appIconColor = config.appIconColor
if (isNougatMR1Plus() && config.lastHandledShortcutColor != appIconColor) { if (config.lastHandledShortcutColor != appIconColor) {
val newConversation = getCreateNewContactShortcut(appIconColor) val newConversation = getCreateNewContactShortcut(appIconColor)
val manager = getSystemService(ShortcutManager::class.java) val manager = getSystemService(ShortcutManager::class.java)

View file

@ -1,9 +1,7 @@
package org.fossify.messages.activities package org.fossify.messages.activities
import android.annotation.TargetApi
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.DocumentsContract import android.provider.DocumentsContract
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@ -16,7 +14,9 @@ import org.fossify.commons.dialogs.FeatureLockedDialog
import org.fossify.commons.dialogs.RadioGroupDialog import org.fossify.commons.dialogs.RadioGroupDialog
import org.fossify.commons.dialogs.SecurityDialog import org.fossify.commons.dialogs.SecurityDialog
import org.fossify.commons.extensions.addLockedLabelIfNeeded import org.fossify.commons.extensions.addLockedLabelIfNeeded
import org.fossify.commons.extensions.beGone
import org.fossify.commons.extensions.beGoneIf import org.fossify.commons.extensions.beGoneIf
import org.fossify.commons.extensions.beVisible
import org.fossify.commons.extensions.beVisibleIf import org.fossify.commons.extensions.beVisibleIf
import org.fossify.commons.extensions.getBlockedNumbers import org.fossify.commons.extensions.getBlockedNumbers
import org.fossify.commons.extensions.getCustomizeColorsString import org.fossify.commons.extensions.getCustomizeColorsString
@ -36,8 +36,6 @@ import org.fossify.commons.helpers.NavigationIcon
import org.fossify.commons.helpers.PROTECTION_FINGERPRINT import org.fossify.commons.helpers.PROTECTION_FINGERPRINT
import org.fossify.commons.helpers.SHOW_ALL_TABS import org.fossify.commons.helpers.SHOW_ALL_TABS
import org.fossify.commons.helpers.ensureBackgroundThread import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.commons.helpers.isNougatPlus
import org.fossify.commons.helpers.isOreoPlus
import org.fossify.commons.helpers.isPiePlus import org.fossify.commons.helpers.isPiePlus
import org.fossify.commons.helpers.isTiramisuPlus import org.fossify.commons.helpers.isTiramisuPlus
import org.fossify.commons.models.RadioItem import org.fossify.commons.models.RadioItem
@ -226,7 +224,6 @@ class SettingsActivity : SimpleActivity() {
} }
private fun setupCustomizeNotifications() = binding.apply { private fun setupCustomizeNotifications() = binding.apply {
settingsCustomizeNotificationsHolder.beVisibleIf(isOreoPlus())
settingsCustomizeNotificationsHolder.setOnClickListener { settingsCustomizeNotificationsHolder.setOnClickListener {
launchCustomizeNotificationsIntent() launchCustomizeNotificationsIntent()
} }
@ -247,19 +244,20 @@ class SettingsActivity : SimpleActivity() {
private fun setupLanguage() = binding.apply { private fun setupLanguage() = binding.apply {
settingsLanguage.text = Locale.getDefault().displayLanguage settingsLanguage.text = Locale.getDefault().displayLanguage
settingsLanguageHolder.beVisibleIf(isTiramisuPlus()) if (isTiramisuPlus()) {
settingsLanguageHolder.beVisible()
settingsLanguageHolder.setOnClickListener { settingsLanguageHolder.setOnClickListener {
launchChangeAppLanguageIntent() launchChangeAppLanguageIntent()
} }
} else {
settingsLanguageHolder.beGone()
}
} }
// support for device-wise blocking came on Android 7, rely only on that
@TargetApi(Build.VERSION_CODES.N)
private fun setupManageBlockedNumbers() = binding.apply { private fun setupManageBlockedNumbers() = binding.apply {
settingsManageBlockedNumbers.text = settingsManageBlockedNumbers.text =
addLockedLabelIfNeeded(org.fossify.commons.R.string.manage_blocked_numbers) addLockedLabelIfNeeded(org.fossify.commons.R.string.manage_blocked_numbers)
settingsManageBlockedNumbersHolder.beVisibleIf(isNougatPlus()) settingsManageBlockedNumbersHolder.beVisible()
settingsManageBlockedNumbersHolder.setOnClickListener { settingsManageBlockedNumbersHolder.setOnClickListener {
if (isOrWasThankYouInstalled()) { if (isOrWasThankYouInstalled()) {
Intent(this@SettingsActivity, ManageBlockedNumbersActivity::class.java).apply { Intent(this@SettingsActivity, ManageBlockedNumbersActivity::class.java).apply {

View file

@ -95,8 +95,6 @@ import org.fossify.commons.helpers.PERMISSION_READ_PHONE_STATE
import org.fossify.commons.helpers.SimpleContactsHelper import org.fossify.commons.helpers.SimpleContactsHelper
import org.fossify.commons.helpers.VcfExporter import org.fossify.commons.helpers.VcfExporter
import org.fossify.commons.helpers.ensureBackgroundThread import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.commons.helpers.isNougatPlus
import org.fossify.commons.helpers.isOreoPlus
import org.fossify.commons.helpers.isSPlus import org.fossify.commons.helpers.isSPlus
import org.fossify.commons.models.PhoneNumber import org.fossify.commons.models.PhoneNumber
import org.fossify.commons.models.RadioItem import org.fossify.commons.models.RadioItem
@ -349,7 +347,7 @@ class ThreadActivity : SimpleActivity() {
findItem(R.id.conversation_details).isVisible = conversation != null && !isRecycleBin findItem(R.id.conversation_details).isVisible = conversation != null && !isRecycleBin
findItem(R.id.block_number).title = findItem(R.id.block_number).title =
addLockedLabelIfNeeded(org.fossify.commons.R.string.block_number) addLockedLabelIfNeeded(org.fossify.commons.R.string.block_number)
findItem(R.id.block_number).isVisible = isNougatPlus() && !isRecycleBin findItem(R.id.block_number).isVisible = !isRecycleBin
findItem(R.id.dial_number).isVisible = findItem(R.id.dial_number).isVisible =
participants.size == 1 && !isSpecialNumber() && !isRecycleBin participants.size == 1 && !isSpecialNumber() && !isRecycleBin
findItem(R.id.manage_people).isVisible = !isSpecialNumber() && !isRecycleBin findItem(R.id.manage_people).isVisible = !isSpecialNumber() && !isRecycleBin
@ -983,12 +981,10 @@ class ThreadActivity : SimpleActivity() {
text = getString(R.string.invalid_short_code_desc) text = getString(R.string.invalid_short_code_desc)
) )
} }
if (isOreoPlus()) {
tooltipText = getString(org.fossify.commons.R.string.more_info) tooltipText = getString(org.fossify.commons.R.string.more_info)
} }
} }
} }
}
private fun setupThreadTitle() { private fun setupThreadTitle() {
val title = conversation?.title val title = conversation?.title

View file

@ -5,21 +5,34 @@ import android.text.TextUtils
import android.view.Menu import android.view.Menu
import org.fossify.commons.dialogs.ConfirmationDialog import org.fossify.commons.dialogs.ConfirmationDialog
import org.fossify.commons.dialogs.FeatureLockedDialog import org.fossify.commons.dialogs.FeatureLockedDialog
import org.fossify.commons.extensions.* import org.fossify.commons.extensions.addBlockedNumber
import org.fossify.commons.extensions.addLockedLabelIfNeeded
import org.fossify.commons.extensions.copyToClipboard
import org.fossify.commons.extensions.isOrWasThankYouInstalled
import org.fossify.commons.extensions.launchActivityIntent
import org.fossify.commons.extensions.notificationManager
import org.fossify.commons.helpers.KEY_PHONE import org.fossify.commons.helpers.KEY_PHONE
import org.fossify.commons.helpers.ensureBackgroundThread import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.commons.helpers.isNougatPlus
import org.fossify.commons.views.MyRecyclerView import org.fossify.commons.views.MyRecyclerView
import org.fossify.messages.R import org.fossify.messages.R
import org.fossify.messages.activities.SimpleActivity import org.fossify.messages.activities.SimpleActivity
import org.fossify.messages.dialogs.RenameConversationDialog import org.fossify.messages.dialogs.RenameConversationDialog
import org.fossify.messages.extensions.* import org.fossify.messages.extensions.config
import org.fossify.messages.extensions.deleteConversation
import org.fossify.messages.extensions.dialNumber
import org.fossify.messages.extensions.markThreadMessagesRead
import org.fossify.messages.extensions.markThreadMessagesUnread
import org.fossify.messages.extensions.renameConversation
import org.fossify.messages.extensions.updateConversationArchivedStatus
import org.fossify.messages.helpers.refreshMessages import org.fossify.messages.helpers.refreshMessages
import org.fossify.messages.messaging.isShortCodeWithLetters import org.fossify.messages.messaging.isShortCodeWithLetters
import org.fossify.messages.models.Conversation import org.fossify.messages.models.Conversation
class ConversationsAdapter( class ConversationsAdapter(
activity: SimpleActivity, recyclerView: MyRecyclerView, onRefresh: () -> Unit, itemClick: (Any) -> Unit activity: SimpleActivity,
recyclerView: MyRecyclerView,
onRefresh: () -> Unit,
itemClick: (Any) -> Unit
) : BaseConversationsAdapter(activity, recyclerView, onRefresh, itemClick) { ) : BaseConversationsAdapter(activity, recyclerView, onRefresh, itemClick) {
override fun getActionMenuId() = R.menu.cab_conversations override fun getActionMenuId() = R.menu.cab_conversations
@ -31,12 +44,16 @@ class ConversationsAdapter(
val archiveAvailable = activity.config.isArchiveAvailable val archiveAvailable = activity.config.isArchiveAvailable
menu.apply { menu.apply {
findItem(R.id.cab_block_number).title = activity.addLockedLabelIfNeeded(org.fossify.commons.R.string.block_number) findItem(R.id.cab_block_number).title =
findItem(R.id.cab_block_number).isVisible = isNougatPlus() activity.addLockedLabelIfNeeded(org.fossify.commons.R.string.block_number)
findItem(R.id.cab_add_number_to_contact).isVisible = isSingleSelection && !isGroupConversation findItem(R.id.cab_add_number_to_contact).isVisible =
findItem(R.id.cab_dial_number).isVisible = isSingleSelection && !isGroupConversation && !isShortCodeWithLetters(selectedConversation.phoneNumber) isSingleSelection && !isGroupConversation
findItem(R.id.cab_dial_number).isVisible =
isSingleSelection && !isGroupConversation &&
!isShortCodeWithLetters(selectedConversation.phoneNumber)
findItem(R.id.cab_copy_number).isVisible = isSingleSelection && !isGroupConversation findItem(R.id.cab_copy_number).isVisible = isSingleSelection && !isGroupConversation
findItem(R.id.cab_rename_conversation).isVisible = isSingleSelection && isGroupConversation findItem(R.id.cab_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 }
findItem(R.id.cab_archive).isVisible = archiveAvailable findItem(R.id.cab_archive).isVisible = archiveAvailable
@ -76,7 +93,10 @@ class ConversationsAdapter(
private fun askConfirmBlock() { private fun askConfirmBlock() {
val numbers = getSelectedItems().distinctBy { it.phoneNumber }.map { it.phoneNumber } val numbers = getSelectedItems().distinctBy { it.phoneNumber }.map { it.phoneNumber }
val numbersString = TextUtils.join(", ", numbers) val numbersString = TextUtils.join(", ", numbers)
val question = String.format(resources.getString(org.fossify.commons.R.string.block_confirmation), numbersString) val question = String.format(
resources.getString(org.fossify.commons.R.string.block_confirmation),
numbersString
)
ConfirmationDialog(activity, question) { ConfirmationDialog(activity, question) {
blockNumbers() blockNumbers()
@ -149,7 +169,8 @@ class ConversationsAdapter(
return return
} }
val conversationsToRemove = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation> val conversationsToRemove =
currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
conversationsToRemove.forEach { conversationsToRemove.forEach {
activity.updateConversationArchivedStatus(it.threadId, true) activity.updateConversationArchivedStatus(it.threadId, true)
activity.notificationManager.cancel(it.threadId.hashCode()) activity.notificationManager.cancel(it.threadId.hashCode())
@ -179,7 +200,8 @@ class ConversationsAdapter(
return return
} }
val conversationsToRemove = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation> val conversationsToRemove =
currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
conversationsToRemove.forEach { conversationsToRemove.forEach {
activity.deleteConversation(it.threadId) activity.deleteConversation(it.threadId)
activity.notificationManager.cancel(it.threadId.hashCode()) activity.notificationManager.cancel(it.threadId.hashCode())
@ -224,7 +246,8 @@ class ConversationsAdapter(
return return
} }
val conversationsMarkedAsRead = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation> val conversationsMarkedAsRead =
currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
ensureBackgroundThread { ensureBackgroundThread {
conversationsMarkedAsRead.filter { conversation -> !conversation.read }.forEach { conversationsMarkedAsRead.filter { conversation -> !conversation.read }.forEach {
activity.markThreadMessagesRead(it.threadId) activity.markThreadMessagesRead(it.threadId)
@ -239,7 +262,8 @@ class ConversationsAdapter(
return return
} }
val conversationsMarkedAsUnread = currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation> val conversationsMarkedAsUnread =
currentList.filter { selectedKeys.contains(it.hashCode()) } as ArrayList<Conversation>
ensureBackgroundThread { ensureBackgroundThread {
conversationsMarkedAsUnread.filter { conversation -> conversation.read }.forEach { conversationsMarkedAsUnread.filter { conversation -> conversation.read }.forEach {
activity.markThreadMessagesUnread(it.threadId) activity.markThreadMessagesUnread(it.threadId)
@ -280,8 +304,10 @@ class ConversationsAdapter(
private fun checkPinBtnVisibility(menu: Menu) { private fun checkPinBtnVisibility(menu: Menu) {
val pinnedConversations = activity.config.pinnedConversations val pinnedConversations = activity.config.pinnedConversations
val selectedConversations = getSelectedItems() val selectedConversations = getSelectedItems()
menu.findItem(R.id.cab_pin_conversation).isVisible = selectedConversations.any { !pinnedConversations.contains(it.threadId.toString()) } menu.findItem(R.id.cab_pin_conversation).isVisible =
menu.findItem(R.id.cab_unpin_conversation).isVisible = selectedConversations.any { pinnedConversations.contains(it.threadId.toString()) } selectedConversations.any { !pinnedConversations.contains(it.threadId.toString()) }
menu.findItem(R.id.cab_unpin_conversation).isVisible =
selectedConversations.any { pinnedConversations.contains(it.threadId.toString()) }
} }
private fun refreshConversations() { private fun refreshConversations() {

View file

@ -14,21 +14,49 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.provider.ContactsContract.PhoneLookup import android.provider.ContactsContract.PhoneLookup
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.provider.Telephony.* import android.provider.Telephony.Mms
import android.provider.Telephony.MmsSms
import android.provider.Telephony.Sms
import android.provider.Telephony.Threads
import android.provider.Telephony.ThreadsColumns
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import android.text.TextUtils 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 me.leolin.shortcutbadger.ShortcutBadger import me.leolin.shortcutbadger.ShortcutBadger
import org.fossify.commons.extensions.* import org.fossify.commons.extensions.areDigitsOnly
import org.fossify.commons.helpers.* import org.fossify.commons.extensions.getBlockedNumbers
import org.fossify.commons.extensions.getIntValue
import org.fossify.commons.extensions.getLongValue
import org.fossify.commons.extensions.getMyContactsCursor
import org.fossify.commons.extensions.getStringValue
import org.fossify.commons.extensions.hasPermission
import org.fossify.commons.extensions.isNumberBlocked
import org.fossify.commons.extensions.normalizeString
import org.fossify.commons.extensions.notificationManager
import org.fossify.commons.extensions.queryCursor
import org.fossify.commons.extensions.showErrorToast
import org.fossify.commons.extensions.toInt
import org.fossify.commons.extensions.toast
import org.fossify.commons.extensions.trimToComparableNumber
import org.fossify.commons.helpers.DAY_SECONDS
import org.fossify.commons.helpers.MONTH_SECONDS
import org.fossify.commons.helpers.MyContactsContentProvider
import org.fossify.commons.helpers.PERMISSION_READ_CONTACTS
import org.fossify.commons.helpers.SimpleContactsHelper
import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.commons.helpers.isQPlus
import org.fossify.commons.models.PhoneNumber import org.fossify.commons.models.PhoneNumber
import org.fossify.commons.models.SimpleContact import org.fossify.commons.models.SimpleContact
import org.fossify.messages.R import org.fossify.messages.R
import org.fossify.messages.databases.MessagesDatabase import org.fossify.messages.databases.MessagesDatabase
import org.fossify.messages.helpers.*
import org.fossify.messages.helpers.AttachmentUtils.parseAttachmentNames import org.fossify.messages.helpers.AttachmentUtils.parseAttachmentNames
import org.fossify.messages.helpers.Config
import org.fossify.messages.helpers.FILE_SIZE_NONE
import org.fossify.messages.helpers.MESSAGES_LIMIT
import org.fossify.messages.helpers.NotificationHelper
import org.fossify.messages.helpers.generateRandomId
import org.fossify.messages.interfaces.AttachmentsDao import org.fossify.messages.interfaces.AttachmentsDao
import org.fossify.messages.interfaces.ConversationsDao import org.fossify.messages.interfaces.ConversationsDao
import org.fossify.messages.interfaces.MessageAttachmentsDao import org.fossify.messages.interfaces.MessageAttachmentsDao
@ -36,7 +64,12 @@ import org.fossify.messages.interfaces.MessagesDao
import org.fossify.messages.messaging.MessagingUtils import org.fossify.messages.messaging.MessagingUtils
import org.fossify.messages.messaging.MessagingUtils.Companion.ADDRESS_SEPARATOR import org.fossify.messages.messaging.MessagingUtils.Companion.ADDRESS_SEPARATOR
import org.fossify.messages.messaging.SmsSender import org.fossify.messages.messaging.SmsSender
import org.fossify.messages.models.* import org.fossify.messages.models.Attachment
import org.fossify.messages.models.Conversation
import org.fossify.messages.models.Message
import org.fossify.messages.models.MessageAttachment
import org.fossify.messages.models.NamePhoto
import org.fossify.messages.models.RecycleBinMessage
import java.io.FileNotFoundException import java.io.FileNotFoundException
val Context.config: Config get() = Config.newInstance(applicationContext) val Context.config: Config get() = Config.newInstance(applicationContext)
@ -114,7 +147,15 @@ fun Context.getMessages(
val participants = senderNumber.split(ADDRESS_SEPARATOR).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()
)
} }
val isMMS = false val isMMS = false
val message = val message =
@ -159,7 +200,12 @@ fun Context.getMessages(
} }
// as soon as a message contains multiple recipients it counts as an MMS instead of SMS // as soon as a message contains multiple recipients it counts as an MMS instead of SMS
fun Context.getMMS(threadId: Long? = null, getImageResolutions: Boolean = false, sortOrder: String? = null, dateFrom: Int = -1): ArrayList<Message> { fun Context.getMMS(
threadId: Long? = null,
getImageResolutions: Boolean = false,
sortOrder: String? = null,
dateFrom: Int = -1
): ArrayList<Message> {
val uri = Mms.CONTENT_URI val uri = Mms.CONTENT_URI
val projection = arrayOf( val projection = arrayOf(
Mms._ID, Mms._ID,
@ -175,7 +221,8 @@ fun Context.getMMS(threadId: Long? = null, getImageResolutions: Boolean = false,
var selectionArgs: Array<String>? = null var selectionArgs: Array<String>? = null
if (threadId == null && dateFrom != -1) { if (threadId == null && dateFrom != -1) {
selection = "${Sms.DATE} < ${dateFrom.toLong()}" //Should not multiply 1000 here, because date in mms's database is different from sms's. selection =
"${Sms.DATE} < ${dateFrom.toLong()}" //Should not multiply 1000 here, because date in mms's database is different from sms's.
} else if (threadId != null && dateFrom == -1) { } else if (threadId != null && dateFrom == -1) {
selection = "${Sms.THREAD_ID} = ?" selection = "${Sms.THREAD_ID} = ?"
selectionArgs = arrayOf(threadId.toString()) selectionArgs = arrayOf(threadId.toString())
@ -262,7 +309,10 @@ fun Context.getMMSSender(msgId: Long): String {
return "" return ""
} }
fun Context.getConversations(threadId: Long? = null, privateContacts: ArrayList<SimpleContact> = ArrayList()): ArrayList<Conversation> { fun Context.getConversations(
threadId: Long? = null,
privateContacts: ArrayList<SimpleContact> = ArrayList()
): ArrayList<Conversation> {
val archiveAvailable = config.isArchiveAvailable val archiveAvailable = config.isArchiveAvailable
val uri = Uri.parse("${Threads.CONTENT_URI}?simple=true") val uri = Uri.parse("${Threads.CONTENT_URI}?simple=true")
@ -291,7 +341,13 @@ fun Context.getConversations(threadId: Long? = null, privateContacts: ArrayList<
val simpleContactHelper = SimpleContactsHelper(this) val simpleContactHelper = SimpleContactsHelper(this)
val blockedNumbers = getBlockedNumbers() val blockedNumbers = getBlockedNumbers()
try { try {
queryCursorUnsafe(uri, projection.toTypedArray(), selection, selectionArgs, sortOrder) { cursor -> queryCursorUnsafe(
uri,
projection.toTypedArray(),
selection,
selectionArgs,
sortOrder
) { cursor ->
val id = cursor.getLongValue(Threads._ID) val id = cursor.getLongValue(Threads._ID)
var snippet = cursor.getStringValue(Threads.SNIPPET) ?: "" var snippet = cursor.getStringValue(Threads.SNIPPET) ?: ""
if (snippet.isEmpty()) { if (snippet.isEmpty()) {
@ -304,19 +360,39 @@ fun Context.getConversations(threadId: Long? = null, privateContacts: ArrayList<
} }
val rawIds = cursor.getStringValue(Threads.RECIPIENT_IDS) val rawIds = cursor.getStringValue(Threads.RECIPIENT_IDS)
val recipientIds = rawIds.split(" ").filter { it.areDigitsOnly() }.map { it.toInt() }.toMutableList() val recipientIds =
rawIds.split(" ").filter { it.areDigitsOnly() }.map { it.toInt() }.toMutableList()
val phoneNumbers = getThreadPhoneNumbers(recipientIds) val phoneNumbers = getThreadPhoneNumbers(recipientIds)
if (phoneNumbers.isEmpty() || phoneNumbers.any { isNumberBlocked(it, blockedNumbers) }) { if (phoneNumbers.isEmpty() || phoneNumbers.any {
isNumberBlocked(
it,
blockedNumbers
)
}) {
return@queryCursorUnsafe return@queryCursorUnsafe
} }
val names = getThreadContactNames(phoneNumbers, privateContacts) val names = getThreadContactNames(phoneNumbers, privateContacts)
val title = TextUtils.join(", ", names.toTypedArray()) val title = TextUtils.join(", ", names.toTypedArray())
val photoUri = if (phoneNumbers.size == 1) simpleContactHelper.getPhotoUriFromPhoneNumber(phoneNumbers.first()) else "" val photoUri =
if (phoneNumbers.size == 1) simpleContactHelper.getPhotoUriFromPhoneNumber(
phoneNumbers.first()
) else ""
val isGroupConversation = phoneNumbers.size > 1 val isGroupConversation = phoneNumbers.size > 1
val read = cursor.getIntValue(Threads.READ) == 1 val read = cursor.getIntValue(Threads.READ) == 1
val archived = if (archiveAvailable) cursor.getIntValue(Threads.ARCHIVED) == 1 else false val archived =
val conversation = Conversation(id, snippet, date.toInt(), read, title, photoUri, isGroupConversation, phoneNumbers.first(), isArchived = archived) if (archiveAvailable) cursor.getIntValue(Threads.ARCHIVED) == 1 else false
val conversation = Conversation(
id,
snippet,
date.toInt(),
read,
title,
photoUri,
isGroupConversation,
phoneNumbers.first(),
isArchived = archived
)
conversations.add(conversation) conversations.add(conversation)
} }
} catch (sqliteException: SQLiteException) { } catch (sqliteException: SQLiteException) {
@ -399,7 +475,11 @@ fun Context.getMmsAttachment(id: Long, getImageResolutions: Boolean): MessageAtt
try { try {
val options = BitmapFactory.Options() val options = BitmapFactory.Options()
options.inJustDecodeBounds = true options.inJustDecodeBounds = true
BitmapFactory.decodeStream(contentResolver.openInputStream(fileUri), null, options) BitmapFactory.decodeStream(
contentResolver.openInputStream(fileUri),
null,
options
)
width = options.outWidth width = options.outWidth
height = options.outHeight height = options.outHeight
} catch (e: Exception) { } catch (e: Exception) {
@ -410,7 +490,15 @@ fun Context.getMmsAttachment(id: Long, getImageResolutions: Boolean): MessageAtt
messageAttachment.attachments.add(attachment) messageAttachment.attachments.add(attachment)
} else if (mimetype != "application/smil") { } else if (mimetype != "application/smil") {
val attachmentName = attachmentNames?.getOrNull(attachmentCount) ?: "" val attachmentName = attachmentNames?.getOrNull(attachmentCount) ?: ""
val attachment = Attachment(partId, id, Uri.withAppendedPath(uri, partId.toString()).toString(), mimetype, 0, 0, attachmentName) val attachment = Attachment(
partId,
id,
Uri.withAppendedPath(uri, partId.toString()).toString(),
mimetype,
0,
0,
attachmentName
)
messageAttachment.attachments.add(attachment) messageAttachment.attachments.add(attachment)
attachmentCount++ attachmentCount++
} else { } else {
@ -476,7 +564,10 @@ fun Context.getMessageRecipientAddress(messageId: Long): String {
return "" return ""
} }
fun Context.getThreadParticipants(threadId: Long, contactsMap: HashMap<Int, SimpleContact>?): ArrayList<SimpleContact> { fun Context.getThreadParticipants(
threadId: Long,
contactsMap: HashMap<Int, SimpleContact>?
): ArrayList<SimpleContact> {
val uri = Uri.parse("${MmsSms.CONTENT_CONVERSATIONS_URI}?simple=true") val uri = Uri.parse("${MmsSms.CONTENT_CONVERSATIONS_URI}?simple=true")
val projection = arrayOf( val projection = arrayOf(
ThreadsColumns.RECIPIENT_IDS ThreadsColumns.RECIPIENT_IDS
@ -501,7 +592,15 @@ fun Context.getThreadParticipants(threadId: Long, contactsMap: HashMap<Int, Simp
val name = namePhoto.name val name = namePhoto.name
val photoUri = namePhoto.photoUri ?: "" val photoUri = namePhoto.photoUri ?: ""
val phoneNumber = PhoneNumber(number, 0, "", number) val phoneNumber = PhoneNumber(number, 0, "", number)
val contact = SimpleContact(addressId, addressId, name, photoUri, arrayListOf(phoneNumber), ArrayList(), ArrayList()) val contact = SimpleContact(
addressId,
addressId,
name,
photoUri,
arrayListOf(phoneNumber),
ArrayList(),
ArrayList()
)
participants.add(contact) participants.add(contact)
} }
} }
@ -520,7 +619,10 @@ fun Context.getThreadPhoneNumbers(recipientIds: List<Int>): ArrayList<String> {
return numbers return numbers
} }
fun Context.getThreadContactNames(phoneNumbers: List<String>, privateContacts: ArrayList<SimpleContact>): ArrayList<String> { fun Context.getThreadContactNames(
phoneNumbers: List<String>,
privateContacts: ArrayList<SimpleContact>
): ArrayList<String> {
val names = ArrayList<String>() val names = ArrayList<String>()
phoneNumbers.forEach { number -> phoneNumbers.forEach { number ->
val name = SimpleContactsHelper(this).getNameFromPhoneNumber(number) val name = SimpleContactsHelper(this).getNameFromPhoneNumber(number)
@ -578,7 +680,8 @@ fun Context.getSuggestedContacts(privateContacts: ArrayList<SimpleContact>): Arr
return@queryCursor return@queryCursor
} else if (namePhoto.name == senderNumber) { } else if (namePhoto.name == senderNumber) {
if (privateContacts.isNotEmpty()) { if (privateContacts.isNotEmpty()) {
val privateContact = privateContacts.firstOrNull { it.phoneNumbers.first().normalizedNumber == senderNumber } val privateContact =
privateContacts.firstOrNull { it.phoneNumbers.first().normalizedNumber == senderNumber }
if (privateContact != null) { if (privateContact != null) {
senderName = privateContact.name senderName = privateContact.name
photoUri = privateContact.photoUri photoUri = privateContact.photoUri
@ -591,8 +694,17 @@ fun Context.getSuggestedContacts(privateContacts: ArrayList<SimpleContact>): Arr
} }
val phoneNumber = PhoneNumber(senderNumber, 0, "", senderNumber) val phoneNumber = PhoneNumber(senderNumber, 0, "", senderNumber)
val contact = SimpleContact(0, 0, senderName, photoUri, arrayListOf(phoneNumber), ArrayList(), ArrayList()) val contact = SimpleContact(
if (!contacts.map { it.phoneNumbers.first().normalizedNumber.trimToComparableNumber() }.contains(senderNumber.trimToComparableNumber())) { 0,
0,
senderName,
photoUri,
arrayListOf(phoneNumber),
ArrayList(),
ArrayList()
)
if (!contacts.map { it.phoneNumbers.first().normalizedNumber.trimToComparableNumber() }
.contains(senderNumber.trimToComparableNumber())) {
contacts.add(contact) contacts.add(contact)
} }
} }
@ -690,7 +802,7 @@ fun Context.deleteConversation(threadId: Long) {
conversationsDB.deleteThreadId(threadId) conversationsDB.deleteThreadId(threadId)
messagesDB.deleteThreadMessages(threadId) messagesDB.deleteThreadMessages(threadId)
if (config.customNotifications.contains(threadId.toString()) && isOreoPlus()) { if (config.customNotifications.contains(threadId.toString())) {
config.removeCustomNotificationsByThreadId(threadId) config.removeCustomNotificationsByThreadId(threadId)
notificationManager.deleteNotificationChannel(threadId.toString()) notificationManager.deleteNotificationChannel(threadId.toString())
} }
@ -853,13 +965,26 @@ fun Context.getThreadId(addresses: Set<String>): Long {
} }
} }
fun Context.showReceivedMessageNotification(messageId: Long, address: String, body: String, threadId: Long, bitmap: Bitmap?) { fun Context.showReceivedMessageNotification(
messageId: Long,
address: String,
body: String,
threadId: Long,
bitmap: Bitmap?
) {
val privateCursor = getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true) val privateCursor = getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true)
ensureBackgroundThread { ensureBackgroundThread {
val senderName = getNameFromAddress(address, privateCursor) val senderName = getNameFromAddress(address, privateCursor)
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
notificationHelper.showMessageNotification(messageId, address, body, threadId, bitmap, senderName) notificationHelper.showMessageNotification(
messageId,
address,
body,
threadId,
bitmap,
senderName
)
} }
} }
} }
@ -993,7 +1118,8 @@ fun Context.updateLastConversationMessage(threadId: Long) {
// following Android code (which runs even if no messages are deleted): // following Android code (which runs even if no messages are deleted):
// https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/android14-release/src/com/android/providers/telephony/MmsSmsProvider.java#1409 // https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/android14-release/src/com/android/providers/telephony/MmsSmsProvider.java#1409
val uri = Threads.CONTENT_URI val uri = Threads.CONTENT_URI
val selection = "1 = 0" // always-false condition, because we don't actually want to delete any messages val selection =
"1 = 0" // always-false condition, because we don't actually want to delete any messages
try { try {
contentResolver.delete(uri, selection, null) contentResolver.delete(uri, selection, null)
val newConversation = getConversations(threadId)[0] val newConversation = getConversations(threadId)[0]
@ -1017,7 +1143,8 @@ fun Context.getFileSizeFromUri(uri: Uri): Long {
// if "content://" uri scheme, try contentResolver table // if "content://" uri scheme, try contentResolver table
if (uri.scheme.equals(ContentResolver.SCHEME_CONTENT)) { if (uri.scheme.equals(ContentResolver.SCHEME_CONTENT)) {
return contentResolver.query(uri, arrayOf(OpenableColumns.SIZE), null, null, null)?.use { cursor -> return contentResolver.query(uri, arrayOf(OpenableColumns.SIZE), null, null, null)
?.use { cursor ->
// maybe shouldn't trust ContentResolver for size: // maybe shouldn't trust ContentResolver for size:
// https://stackoverflow.com/questions/48302972/content-resolver-returns-wrong-size // https://stackoverflow.com/questions/48302972/content-resolver-returns-wrong-size
val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE) val sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
@ -1061,7 +1188,8 @@ fun Context.insertOrUpdateConversation(
) { ) {
var updatedConv = conversation var updatedConv = conversation
if (cachedConv != null && cachedConv.usesCustomTitle) { if (cachedConv != null && cachedConv.usesCustomTitle) {
updatedConv = updatedConv.copy(title = cachedConv.title, usesCustomTitle = cachedConv.usesCustomTitle) updatedConv =
updatedConv.copy(title = cachedConv.title, usesCustomTitle = cachedConv.usesCustomTitle)
} }
conversationsDB.insertOrUpdate(updatedConv) conversationsDB.insertOrUpdate(updatedConv)
} }
@ -1076,10 +1204,15 @@ fun Context.renameConversation(conversation: Conversation, newTitle: String): Co
return updatedConv return updatedConv
} }
fun Context.createTemporaryThread(message: Message, threadId: Long = generateRandomId(), cachedConv: Conversation?) { fun Context.createTemporaryThread(
message: Message,
threadId: Long = generateRandomId(),
cachedConv: Conversation?
) {
val simpleContactHelper = SimpleContactsHelper(this) val simpleContactHelper = SimpleContactsHelper(this)
val addresses = message.participants.getAddresses() val addresses = message.participants.getAddresses()
val photoUri = if (addresses.size == 1) simpleContactHelper.getPhotoUriFromPhoneNumber(addresses.first()) else "" val photoUri =
if (addresses.size == 1) simpleContactHelper.getPhotoUriFromPhoneNumber(addresses.first()) else ""
val title = if (cachedConv != null && cachedConv.usesCustomTitle) { val title = if (cachedConv != null && cachedConv.usesCustomTitle) {
cachedConv.title cachedConv.title
} else { } else {
@ -1132,4 +1265,5 @@ fun Context.clearExpiredScheduledMessages(threadId: Long, messagesToDelete: List
} }
} }
fun Context.getDefaultKeyboardHeight() = resources.getDimensionPixelSize(R.dimen.default_keyboard_height) fun Context.getDefaultKeyboardHeight() =
resources.getDimensionPixelSize(R.dimen.default_keyboard_height)

View file

@ -17,8 +17,6 @@ import androidx.core.app.RemoteInput
import org.fossify.commons.extensions.getProperPrimaryColor import org.fossify.commons.extensions.getProperPrimaryColor
import org.fossify.commons.extensions.notificationManager import org.fossify.commons.extensions.notificationManager
import org.fossify.commons.helpers.SimpleContactsHelper import org.fossify.commons.helpers.SimpleContactsHelper
import org.fossify.commons.helpers.isNougatPlus
import org.fossify.commons.helpers.isOreoPlus
import org.fossify.messages.R import org.fossify.messages.R
import org.fossify.messages.activities.ThreadActivity import org.fossify.messages.activities.ThreadActivity
import org.fossify.messages.extensions.config import org.fossify.messages.extensions.config
@ -45,10 +43,12 @@ class NotificationHelper(private val context: Context) {
sender: String?, sender: String?,
alertOnlyOnce: Boolean = false alertOnlyOnce: Boolean = false
) { ) {
val hasCustomNotifications = context.config.customNotifications.contains(threadId.toString()) val hasCustomNotifications =
val notificationChannelId = if (hasCustomNotifications) threadId.toString() else NOTIFICATION_CHANNEL_ID context.config.customNotifications.contains(threadId.toString())
val notificationChannelId =
if (hasCustomNotifications) threadId.toString() else NOTIFICATION_CHANNEL_ID
if (!hasCustomNotifications) { if (!hasCustomNotifications) {
maybeCreateChannel(notificationChannelId, context.getString(R.string.channel_received_sms)) createChannel(notificationChannelId, context.getString(R.string.channel_received_sms))
} }
val notificationId = threadId.hashCode() val notificationId = threadId.hashCode()
@ -56,25 +56,40 @@ class NotificationHelper(private val context: Context) {
putExtra(THREAD_ID, threadId) putExtra(THREAD_ID, threadId)
} }
val contentPendingIntent = val contentPendingIntent =
PendingIntent.getActivity(context, notificationId, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE) PendingIntent.getActivity(
context,
notificationId,
contentIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
val markAsReadIntent = Intent(context, MarkAsReadReceiver::class.java).apply { val markAsReadIntent = Intent(context, MarkAsReadReceiver::class.java).apply {
action = MARK_AS_READ action = MARK_AS_READ
putExtra(THREAD_ID, threadId) putExtra(THREAD_ID, threadId)
} }
val markAsReadPendingIntent = val markAsReadPendingIntent =
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
)
val deleteSmsIntent = Intent(context, DeleteSmsReceiver::class.java).apply { val deleteSmsIntent = Intent(context, DeleteSmsReceiver::class.java).apply {
putExtra(THREAD_ID, threadId) putExtra(THREAD_ID, threadId)
putExtra(MESSAGE_ID, messageId) putExtra(MESSAGE_ID, messageId)
} }
val deleteSmsPendingIntent = val deleteSmsPendingIntent =
PendingIntent.getBroadcast(context, notificationId, deleteSmsIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE) PendingIntent.getBroadcast(
context,
notificationId,
deleteSmsIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
var replyAction: NotificationCompat.Action? = null var replyAction: NotificationCompat.Action? = null
val isNoReplySms = isShortCodeWithLetters(address) val isNoReplySms = isShortCodeWithLetters(address)
if (isNougatPlus() && !isNoReplySms) { if (!isNoReplySms) {
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)
@ -92,7 +107,11 @@ class NotificationHelper(private val context: Context) {
replyIntent, replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
) )
replyAction = NotificationCompat.Action.Builder(R.drawable.ic_send_vector, replyLabel, replyPendingIntent) replyAction = NotificationCompat.Action.Builder(
R.drawable.ic_send_vector,
replyLabel,
replyPendingIntent
)
.addRemoteInput(remoteInput) .addRemoteInput(remoteInput)
.build() .build()
} }
@ -113,7 +132,9 @@ class NotificationHelper(private val context: Context) {
setContentTitle(sender) setContentTitle(sender)
setLargeIcon(largeIcon) setLargeIcon(largeIcon)
val summaryText = context.getString(R.string.new_message) val summaryText = context.getString(R.string.new_message)
setStyle(NotificationCompat.BigTextStyle().setSummaryText(summaryText).bigText(body)) setStyle(
NotificationCompat.BigTextStyle().setSummaryText(summaryText).bigText(body)
)
} }
} }
@ -132,7 +153,11 @@ class NotificationHelper(private val context: Context) {
builder.addAction(replyAction) builder.addAction(replyAction)
} }
builder.addAction(org.fossify.commons.R.drawable.ic_check_vector, context.getString(R.string.mark_as_read), markAsReadPendingIntent) builder.addAction(
org.fossify.commons.R.drawable.ic_check_vector,
context.getString(R.string.mark_as_read),
markAsReadPendingIntent
)
.setChannelId(notificationChannelId) .setChannelId(notificationChannelId)
if (isNoReplySms) { if (isNoReplySms) {
builder.addAction( builder.addAction(
@ -146,19 +171,27 @@ class NotificationHelper(private val context: Context) {
@SuppressLint("NewApi") @SuppressLint("NewApi")
fun showSendingFailedNotification(recipientName: String, threadId: Long) { fun showSendingFailedNotification(recipientName: String, threadId: Long) {
val hasCustomNotifications = context.config.customNotifications.contains(threadId.toString()) val hasCustomNotifications =
val notificationChannelId = if (hasCustomNotifications) threadId.toString() else NOTIFICATION_CHANNEL_ID context.config.customNotifications.contains(threadId.toString())
val notificationChannelId =
if (hasCustomNotifications) threadId.toString() else NOTIFICATION_CHANNEL_ID
if (!hasCustomNotifications) { if (!hasCustomNotifications) {
maybeCreateChannel(notificationChannelId, context.getString(R.string.message_not_sent_short)) createChannel(notificationChannelId, context.getString(R.string.message_not_sent_short))
} }
val notificationId = generateRandomId().hashCode() val notificationId = generateRandomId().hashCode()
val intent = Intent(context, ThreadActivity::class.java).apply { val intent = Intent(context, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, threadId) putExtra(THREAD_ID, threadId)
} }
val contentPendingIntent = PendingIntent.getActivity(context, notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE) val contentPendingIntent = PendingIntent.getActivity(
context,
notificationId,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
val summaryText = String.format(context.getString(R.string.message_sending_error), recipientName) val summaryText =
String.format(context.getString(R.string.message_sending_error), recipientName)
val largeIcon = SimpleContactsHelper(context).getContactLetterIcon(recipientName) val largeIcon = SimpleContactsHelper(context).getContactLetterIcon(recipientName)
val builder = NotificationCompat.Builder(context, notificationChannelId) val builder = NotificationCompat.Builder(context, notificationChannelId)
.setContentTitle(context.getString(R.string.message_not_sent_short)) .setContentTitle(context.getString(R.string.message_not_sent_short))
@ -177,8 +210,7 @@ class NotificationHelper(private val context: Context) {
notificationManager.notify(notificationId, builder.build()) notificationManager.notify(notificationId, builder.build())
} }
private fun maybeCreateChannel(id: String, name: String) { private fun createChannel(id: String, name: String) {
if (isOreoPlus()) {
val audioAttributes = AudioAttributes.Builder() val audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION) .setUsage(AudioAttributes.USAGE_NOTIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@ -194,9 +226,13 @@ class NotificationHelper(private val context: Context) {
notificationManager.createNotificationChannel(this) notificationManager.createNotificationChannel(this)
} }
} }
}
private fun getMessagesStyle(address: String, body: String, notificationId: Int, name: String?): NotificationCompat.MessagingStyle { private fun getMessagesStyle(
address: String,
body: String,
notificationId: Int,
name: String?
): NotificationCompat.MessagingStyle {
val sender = if (name != null) { val sender = if (name != null) {
Person.Builder() Person.Builder()
.setName(name) .setName(name)
@ -210,18 +246,20 @@ class NotificationHelper(private val context: Context) {
getOldMessages(notificationId).forEach { getOldMessages(notificationId).forEach {
style.addMessage(it) style.addMessage(it)
} }
val newMessage = NotificationCompat.MessagingStyle.Message(body, System.currentTimeMillis(), sender) val newMessage =
NotificationCompat.MessagingStyle.Message(body, System.currentTimeMillis(), sender)
style.addMessage(newMessage) style.addMessage(newMessage)
} }
} }
private fun getOldMessages(notificationId: Int): List<NotificationCompat.MessagingStyle.Message> { private fun getOldMessages(notificationId: Int): List<NotificationCompat.MessagingStyle.Message> {
if (!isNougatPlus()) { val currentNotification =
return emptyList() notificationManager.activeNotifications.find { it.id == notificationId }
}
val currentNotification = notificationManager.activeNotifications.find { it.id == notificationId }
return if (currentNotification != null) { return if (currentNotification != null) {
val activeStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(currentNotification.notification) val activeStyle =
NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(
currentNotification.notification
)
activeStyle?.messages.orEmpty() activeStyle?.messages.orEmpty()
} else { } else {
emptyList() emptyList()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 890 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Some files were not shown because too many files have changed in this diff Show more