Merge pull request #520 from Naveen3Singh/notification_improvements

Improve and organise notification related code
This commit is contained in:
Tibor Kaputa 2022-12-11 12:26:58 +01:00 committed by GitHub
commit 7ea57de52b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 340 additions and 302 deletions

View File

@ -70,6 +70,7 @@ dependencies {
implementation "me.leolin:ShortcutBadger:1.1.22" implementation "me.leolin:ShortcutBadger:1.1.22"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.googlecode.ez-vcard:ez-vcard:0.11.3' implementation 'com.googlecode.ez-vcard:ez-vcard:0.11.3'
implementation 'androidx.lifecycle:lifecycle-process:2.5.1'
kapt "androidx.room:room-compiler:2.4.3" kapt "androidx.room:room-compiler:2.4.3"
implementation "androidx.room:room-runtime:2.4.3" implementation "androidx.room:room-runtime:2.4.3"

View File

@ -89,15 +89,18 @@ class MainActivity : SimpleActivity() {
super.onResume() super.onResume()
setupToolbar(main_toolbar) setupToolbar(main_toolbar)
getOrCreateConversationsAdapter().apply {
if (storedTextColor != getProperTextColor()) { if (storedTextColor != getProperTextColor()) {
(conversations_list.adapter as? ConversationsAdapter)?.updateTextColor(getProperTextColor()) updateTextColor(getProperTextColor())
} }
if (storedFontSize != config.fontSize) { if (storedFontSize != config.fontSize) {
(conversations_list.adapter as? ConversationsAdapter)?.updateFontSize() updateFontSize()
}
updateDrafts()
} }
(conversations_list.adapter as? ConversationsAdapter)?.updateDrafts()
updateTextColors(main_coordinator) updateTextColors(main_coordinator)
val properPrimaryColor = getProperPrimaryColor() val properPrimaryColor = getProperPrimaryColor()
@ -285,6 +288,25 @@ class MainActivity : SimpleActivity() {
} }
} }
private fun getOrCreateConversationsAdapter(): ConversationsAdapter {
var currAdapter = conversations_list.adapter
if (currAdapter == null) {
hideKeyboard()
currAdapter = ConversationsAdapter(
activity = this,
recyclerView = conversations_list,
onRefresh = { notifyDatasetChanged() },
itemClick = { handleConversationClick(it) }
)
conversations_list.adapter = currAdapter
if (areSystemAnimationsEnabled) {
conversations_list.scheduleLayoutAnimation()
}
}
return currAdapter as ConversationsAdapter
}
private fun setupConversations(conversations: ArrayList<Conversation>) { private fun setupConversations(conversations: ArrayList<Conversation>) {
val hasConversations = conversations.isNotEmpty() val hasConversations = conversations.isNotEmpty()
val sortedConversations = conversations.sortedWith( val sortedConversations = conversations.sortedWith(
@ -301,37 +323,33 @@ class MainActivity : SimpleActivity() {
no_conversations_placeholder_2.beGone() no_conversations_placeholder_2.beGone()
} }
val currAdapter = conversations_list.adapter
if (currAdapter == null) {
hideKeyboard()
ConversationsAdapter(this, conversations_list) {
Intent(this, ThreadActivity::class.java).apply {
val conversation = it as Conversation
putExtra(THREAD_ID, conversation.threadId)
putExtra(THREAD_TITLE, conversation.title)
startActivity(this)
}
}.apply {
conversations_list.adapter = this
updateConversations(sortedConversations)
}
if (areSystemAnimationsEnabled) {
conversations_list.scheduleLayoutAnimation()
}
} else {
try { try {
(currAdapter as ConversationsAdapter).updateConversations(sortedConversations) { getOrCreateConversationsAdapter().apply {
if (currAdapter.currentList.isEmpty()) { updateConversations(sortedConversations) {
if (currentList.isEmpty()) {
conversations_fastscroller.beGone() conversations_fastscroller.beGone()
no_conversations_placeholder.text = getString(R.string.no_conversations_found) no_conversations_placeholder.text = getString(R.string.no_conversations_found)
no_conversations_placeholder.beVisible() no_conversations_placeholder.beVisible()
no_conversations_placeholder_2.beVisible() no_conversations_placeholder_2.beVisible()
} }
} }
}
} catch (ignored: Exception) { } catch (ignored: Exception) {
} }
} }
@SuppressLint("NotifyDataSetChanged")
private fun notifyDatasetChanged() {
getOrCreateConversationsAdapter().notifyDataSetChanged()
}
private fun handleConversationClick(any: Any) {
Intent(this, ThreadActivity::class.java).apply {
val conversation = any as Conversation
putExtra(THREAD_ID, conversation.threadId)
putExtra(THREAD_TITLE, conversation.title)
startActivity(this)
}
} }
private fun launchNewConversation() { private fun launchNewConversation() {

View File

@ -1,5 +1,6 @@
package com.simplemobiletools.smsmessenger.adapters package com.simplemobiletools.smsmessenger.adapters
import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.graphics.Typeface import android.graphics.Typeface
import android.os.Parcelable import android.os.Parcelable
@ -31,8 +32,9 @@ import com.simplemobiletools.smsmessenger.models.Conversation
import kotlinx.android.synthetic.main.item_conversation.view.* import kotlinx.android.synthetic.main.item_conversation.view.*
class ConversationsAdapter( class ConversationsAdapter(
activity: SimpleActivity, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit activity: SimpleActivity, recyclerView: MyRecyclerView, onRefresh: () -> Unit, itemClick: (Any) -> Unit
) : MyRecyclerViewListAdapter<Conversation>(activity, recyclerView, ConversationDiffCallback(), itemClick), RecyclerViewFastScroller.OnPopupTextUpdate { ) : MyRecyclerViewListAdapter<Conversation>(activity, recyclerView, ConversationDiffCallback(), itemClick, onRefresh),
RecyclerViewFastScroller.OnPopupTextUpdate {
private var fontSize = activity.getTextSize() private var fontSize = activity.getTextSize()
private var drafts = HashMap<Long, String?>() private var drafts = HashMap<Long, String?>()
@ -40,7 +42,9 @@ class ConversationsAdapter(
init { init {
setupDragListener(true) setupDragListener(true)
ensureBackgroundThread {
fetchDrafts(drafts) fetchDrafts(drafts)
}
setHasStableIds(true) setHasStableIds(true)
registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
@ -314,13 +318,17 @@ class ConversationsAdapter(
} }
fun updateDrafts() { fun updateDrafts() {
ensureBackgroundThread {
val newDrafts = HashMap<Long, String?>() val newDrafts = HashMap<Long, String?>()
fetchDrafts(newDrafts) fetchDrafts(newDrafts)
if (drafts.hashCode() != newDrafts.hashCode()) { if (drafts.hashCode() != newDrafts.hashCode()) {
drafts = newDrafts drafts = newDrafts
activity.runOnUiThread {
notifyDataSetChanged() notifyDataSetChanged()
} }
} }
}
}
private fun setupView(view: View, conversation: Conversation) { private fun setupView(view: View, conversation: Conversation) {
view.apply { view.apply {

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.Typeface import android.graphics.Typeface
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.Size import android.util.Size
@ -18,7 +19,6 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.FitCenter import com.bumptech.glide.load.resource.bitmap.FitCenter
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target import com.bumptech.glide.request.target.Target
@ -291,7 +291,20 @@ class ThreadAdapter(
thread_message_body.setLinkTextColor(context.getProperPrimaryColor()) thread_message_body.setLinkTextColor(context.getProperPrimaryColor())
if (!activity.isFinishing && !activity.isDestroyed) { if (!activity.isFinishing && !activity.isDestroyed) {
SimpleContactsHelper(context).loadContactImage(message.senderPhotoUri, thread_message_sender_photo, message.senderName) val contactLetterIcon = SimpleContactsHelper(context).getContactLetterIcon(message.senderName)
val placeholder = BitmapDrawable(context.resources, contactLetterIcon)
val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.error(placeholder)
.centerCrop()
Glide.with(context)
.load(message.senderPhotoUri)
.placeholder(placeholder)
.apply(options)
.apply(RequestOptions.circleCropTransform())
.into(thread_message_sender_photo)
} }
} }
} }
@ -341,7 +354,6 @@ class ThreadAdapter(
var builder = Glide.with(context) var builder = Glide.with(context)
.load(uri) .load(uri)
.transition(DrawableTransitionOptions.withCrossFade())
.apply(options) .apply(options)
.listener(object : RequestListener<Drawable> { .listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean { override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {

View File

@ -1,22 +1,13 @@
package com.simplemobiletools.smsmessenger.extensions package com.simplemobiletools.smsmessenger.extensions
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.ContentResolver import android.content.ContentResolver
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.Intent
import android.database.Cursor import android.database.Cursor
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.RingtoneManager
import android.net.Uri import android.net.Uri
import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.provider.ContactsContract.PhoneLookup import android.provider.ContactsContract.PhoneLookup
@ -24,15 +15,15 @@ import android.provider.OpenableColumns
import android.provider.Telephony.* import android.provider.Telephony.*
import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager
import android.text.TextUtils import android.text.TextUtils
import androidx.core.app.NotificationCompat import com.bumptech.glide.Glide
import androidx.core.app.RemoteInput import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.klinker.android.send_message.Transaction.getAddressSeparator 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
import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.ThreadActivity
import com.simplemobiletools.smsmessenger.databases.MessagesDatabase import com.simplemobiletools.smsmessenger.databases.MessagesDatabase
import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.helpers.AttachmentUtils.parseAttachmentNames import com.simplemobiletools.smsmessenger.helpers.AttachmentUtils.parseAttachmentNames
@ -41,8 +32,6 @@ 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.models.* import com.simplemobiletools.smsmessenger.models.*
import com.simplemobiletools.smsmessenger.receivers.DirectReplyReceiver
import com.simplemobiletools.smsmessenger.receivers.MarkAsReadReceiver
import me.leolin.shortcutbadger.ShortcutBadger import me.leolin.shortcutbadger.ShortcutBadger
import java.io.FileNotFoundException import java.io.FileNotFoundException
@ -58,6 +47,8 @@ val Context.messageAttachmentsDB: MessageAttachmentsDao get() = getMessagesDB().
val Context.messagesDB: MessagesDao get() = getMessagesDB().MessagesDao() val Context.messagesDB: MessagesDao get() = getMessagesDB().MessagesDao()
val Context.notificationHelper get() = NotificationHelper(this)
fun Context.getMessages( fun Context.getMessages(
threadId: Long, threadId: Long,
getImageResolutions: Boolean, getImageResolutions: Boolean,
@ -713,12 +704,12 @@ fun Context.getThreadId(addresses: Set<String>): Long {
} }
fun Context.showReceivedMessageNotification(address: String, body: String, threadId: Long, bitmap: Bitmap?) { fun Context.showReceivedMessageNotification(address: String, body: String, threadId: Long, bitmap: Bitmap?) {
val privateCursor = getMyContactsCursor(false, 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 {
showMessageNotification(address, body, threadId, bitmap, senderName) notificationHelper.showMessageNotification(address, body, threadId, bitmap, senderName)
} }
} }
} }
@ -746,130 +737,26 @@ fun Context.getContactFromAddress(address: String, callback: ((contact: SimpleCo
} }
} }
@SuppressLint("NewApi") fun Context.getNotificationBitmap(photoUri: String): Bitmap? {
fun Context.showMessageNotification(address: String, body: String, threadId: Long, bitmap: Bitmap?, sender: String) { val size = resources.getDimension(R.dimen.notification_large_icon_size).toInt()
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (photoUri.isEmpty()) {
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) return null
if (isOreoPlus()) {
val audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
.build()
val name = getString(R.string.channel_received_sms)
val importance = NotificationManager.IMPORTANCE_HIGH
NotificationChannel(NOTIFICATION_CHANNEL, name, importance).apply {
setBypassDnd(false)
enableLights(true)
setSound(soundUri, audioAttributes)
enableVibration(true)
notificationManager.createNotificationChannel(this)
}
} }
val intent = Intent(this, ThreadActivity::class.java).apply { val options = RequestOptions()
putExtra(THREAD_ID, threadId) .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
} .centerCrop()
val pendingIntent = PendingIntent.getActivity(this, threadId.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE) return try {
val summaryText = getString(R.string.new_message) Glide.with(this)
val markAsReadIntent = Intent(this, MarkAsReadReceiver::class.java).apply { .asBitmap()
action = MARK_AS_READ .load(photoUri)
putExtra(THREAD_ID, threadId) .apply(options)
} .apply(RequestOptions.circleCropTransform())
.into(size, size)
val markAsReadPendingIntent = .get()
PendingIntent.getBroadcast(this, threadId.hashCode(), markAsReadIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE) } catch (e: Exception) {
var replyAction: NotificationCompat.Action? = null null
if (isNougatPlus()) {
val replyLabel = getString(R.string.reply)
val remoteInput = RemoteInput.Builder(REPLY)
.setLabel(replyLabel)
.build()
val replyIntent = Intent(this, DirectReplyReceiver::class.java).apply {
putExtra(THREAD_ID, threadId)
putExtra(THREAD_NUMBER, address)
}
val replyPendingIntent =
PendingIntent.getBroadcast(applicationContext, threadId.hashCode(), replyIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
replyAction = NotificationCompat.Action.Builder(R.drawable.ic_send_vector, replyLabel, replyPendingIntent)
.addRemoteInput(remoteInput)
.build()
}
val largeIcon = bitmap ?: SimpleContactsHelper(this).getContactLetterIcon(sender)
val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL).apply {
when (config.lockScreenVisibilitySetting) {
LOCK_SCREEN_SENDER_MESSAGE -> {
setLargeIcon(largeIcon)
setStyle(getMessagesStyle(notificationManager, threadId, sender, body))
}
LOCK_SCREEN_SENDER -> {
setContentTitle(sender)
setLargeIcon(largeIcon)
setStyle(NotificationCompat.BigTextStyle().setSummaryText(summaryText).bigText(body))
}
}
color = getProperPrimaryColor()
setSmallIcon(R.drawable.ic_messenger)
setContentIntent(pendingIntent)
priority = NotificationCompat.PRIORITY_MAX
setDefaults(Notification.DEFAULT_LIGHTS)
setCategory(Notification.CATEGORY_MESSAGE)
setAutoCancel(true)
setSound(soundUri, AudioManager.STREAM_NOTIFICATION)
}
if (replyAction != null && config.lockScreenVisibilitySetting == LOCK_SCREEN_SENDER_MESSAGE) {
builder.addAction(replyAction)
}
builder.addAction(R.drawable.ic_check_vector, getString(R.string.mark_as_read), markAsReadPendingIntent)
.setChannelId(NOTIFICATION_CHANNEL)
notificationManager.notify(threadId.hashCode(), builder.build())
}
private fun Context.getMessagesStyle(
notificationManager: NotificationManager,
threadId: Long,
sender: String,
body: String
): NotificationCompat.MessagingStyle {
val oldMessages = getOldMessages(notificationManager, threadId)
val messages = NotificationCompat.MessagingStyle(getString(R.string.me))
oldMessages.forEach {
messages.addMessage(it)
}
val currentMessage = NotificationCompat.MessagingStyle.Message(body, System.currentTimeMillis(), sender)
messages.addMessage(currentMessage)
return messages
}
private fun getOldMessages(notificationManager: NotificationManager, threadId: Long): List<NotificationCompat.MessagingStyle.Message> {
if (!isNougatPlus()) {
return arrayListOf()
}
val currentNotification = notificationManager.activeNotifications.find { it.id == threadId.hashCode() }
return if (currentNotification != null) {
val messages = currentNotification.notification.extras.getParcelableArray(NotificationCompat.EXTRA_MESSAGES)
val result = arrayListOf<NotificationCompat.MessagingStyle.Message>()
messages?.forEach {
val bundle = it as Bundle
val sender = bundle.getCharSequence("sender")
val text = bundle.getCharSequence("text")
val time = bundle.getLong("time")
val message = NotificationCompat.MessagingStyle.Message(text, time, sender)
result.add(message)
}
return result
} else {
arrayListOf()
} }
} }

View File

@ -0,0 +1,198 @@
package com.simplemobiletools.smsmessenger.helpers
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.RingtoneManager
import androidx.core.app.NotificationCompat
import androidx.core.app.Person
import androidx.core.app.RemoteInput
import com.simplemobiletools.commons.extensions.getProperPrimaryColor
import com.simplemobiletools.commons.extensions.notificationManager
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.commons.helpers.isOreoPlus
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.ThreadActivity
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.receivers.DirectReplyReceiver
import com.simplemobiletools.smsmessenger.receivers.MarkAsReadReceiver
class NotificationHelper(private val context: Context) {
private val notificationManager = context.notificationManager
private val soundUri get() = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
private val user = Person.Builder()
.setName(context.getString(R.string.me))
.build()
@SuppressLint("NewApi")
fun showMessageNotification(address: String, body: String, threadId: Long, bitmap: Bitmap?, sender: String?, alertOnlyOnce: Boolean = false) {
maybeCreateChannel(name = context.getString(R.string.channel_received_sms))
val notificationId = threadId.hashCode()
val contentIntent = Intent(context, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, threadId)
}
val contentPendingIntent =
PendingIntent.getActivity(context, notificationId, contentIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
val markAsReadIntent = Intent(context, MarkAsReadReceiver::class.java).apply {
action = MARK_AS_READ
putExtra(THREAD_ID, threadId)
}
val markAsReadPendingIntent =
PendingIntent.getBroadcast(context, notificationId, markAsReadIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
var replyAction: NotificationCompat.Action? = null
if (isNougatPlus()) {
val replyLabel = context.getString(R.string.reply)
val remoteInput = RemoteInput.Builder(REPLY)
.setLabel(replyLabel)
.build()
val replyIntent = Intent(context, DirectReplyReceiver::class.java).apply {
putExtra(THREAD_ID, threadId)
putExtra(THREAD_NUMBER, address)
}
val replyPendingIntent =
PendingIntent.getBroadcast(
context.applicationContext,
notificationId,
replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
replyAction = NotificationCompat.Action.Builder(R.drawable.ic_send_vector, replyLabel, replyPendingIntent)
.addRemoteInput(remoteInput)
.build()
}
val largeIcon = bitmap ?: if (sender != null) {
SimpleContactsHelper(context).getContactLetterIcon(sender)
} else {
null
}
val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL).apply {
when (context.config.lockScreenVisibilitySetting) {
LOCK_SCREEN_SENDER_MESSAGE -> {
setLargeIcon(largeIcon)
setStyle(getMessagesStyle(address, body, notificationId, sender))
}
LOCK_SCREEN_SENDER -> {
setContentTitle(sender)
setLargeIcon(largeIcon)
val summaryText = context.getString(R.string.new_message)
setStyle(NotificationCompat.BigTextStyle().setSummaryText(summaryText).bigText(body))
}
}
color = context.getProperPrimaryColor()
setSmallIcon(R.drawable.ic_messenger)
setContentIntent(contentPendingIntent)
priority = NotificationCompat.PRIORITY_MAX
setDefaults(Notification.DEFAULT_LIGHTS)
setCategory(Notification.CATEGORY_MESSAGE)
setAutoCancel(true)
setOnlyAlertOnce(alertOnlyOnce)
setSound(soundUri, AudioManager.STREAM_NOTIFICATION)
}
if (replyAction != null && context.config.lockScreenVisibilitySetting == LOCK_SCREEN_SENDER_MESSAGE) {
builder.addAction(replyAction)
}
builder.addAction(R.drawable.ic_check_vector, context.getString(R.string.mark_as_read), markAsReadPendingIntent)
.setChannelId(NOTIFICATION_CHANNEL)
notificationManager.notify(notificationId, builder.build())
}
@SuppressLint("NewApi")
fun showSendingFailedNotification(recipientName: String, threadId: Long) {
maybeCreateChannel(name = context.getString(R.string.message_not_sent_short))
val notificationId = generateRandomId().hashCode()
val intent = Intent(context, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, threadId)
}
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 largeIcon = SimpleContactsHelper(context).getContactLetterIcon(recipientName)
val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL)
.setContentTitle(context.getString(R.string.message_not_sent_short))
.setContentText(summaryText)
.setColor(context.getProperPrimaryColor())
.setSmallIcon(R.drawable.ic_messenger)
.setLargeIcon(largeIcon)
.setStyle(NotificationCompat.BigTextStyle().bigText(summaryText))
.setContentIntent(contentPendingIntent)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(Notification.DEFAULT_LIGHTS)
.setCategory(Notification.CATEGORY_MESSAGE)
.setAutoCancel(true)
.setChannelId(NOTIFICATION_CHANNEL)
notificationManager.notify(notificationId, builder.build())
}
private fun maybeCreateChannel(name: String) {
if (isOreoPlus()) {
val audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
.build()
val id = NOTIFICATION_CHANNEL
val importance = IMPORTANCE_HIGH
NotificationChannel(id, name, importance).apply {
setBypassDnd(false)
enableLights(true)
setSound(soundUri, audioAttributes)
enableVibration(true)
notificationManager.createNotificationChannel(this)
}
}
}
private fun getMessagesStyle(address: String, body: String, notificationId: Int, name: String?): NotificationCompat.MessagingStyle {
val sender = if (name != null) {
Person.Builder()
.setName(name)
.setKey(address)
.build()
} else {
null
}
return NotificationCompat.MessagingStyle(user).also { style ->
getOldMessages(notificationId).forEach {
style.addMessage(it)
}
val newMessage = NotificationCompat.MessagingStyle.Message(body, System.currentTimeMillis(), sender)
style.addMessage(newMessage)
}
}
private fun getOldMessages(notificationId: Int): List<NotificationCompat.MessagingStyle.Message> {
if (!isNougatPlus()) {
return emptyList()
}
val currentNotification = notificationManager.activeNotifications.find { it.id == notificationId }
return if (currentNotification != null) {
val activeStyle = NotificationCompat.MessagingStyle.extractMessagingStyleFromNotification(currentNotification.notification)
activeStyle?.messages.orEmpty()
} else {
emptyList()
}
}
}

View File

@ -4,58 +4,58 @@ import android.annotation.SuppressLint
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Handler
import android.os.Looper
import androidx.core.app.RemoteInput import androidx.core.app.RemoteInput
import com.klinker.android.send_message.Transaction
import com.simplemobiletools.commons.extensions.notificationManager
import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
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.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.getSendMessageSettings import com.simplemobiletools.smsmessenger.helpers.sendMessage
class DirectReplyReceiver : BroadcastReceiver() { class DirectReplyReceiver : BroadcastReceiver() {
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
val address = intent.getStringExtra(THREAD_NUMBER) val address = intent.getStringExtra(THREAD_NUMBER)
val threadId = intent.getLongExtra(THREAD_ID, 0L) val threadId = intent.getLongExtra(THREAD_ID, 0L)
var msg = RemoteInput.getResultsFromIntent(intent)?.getCharSequence(REPLY)?.toString() ?: return var body = RemoteInput.getResultsFromIntent(intent)?.getCharSequence(REPLY)?.toString() ?: return
msg = context.removeDiacriticsIfNeeded(msg) body = context.removeDiacriticsIfNeeded(body)
val settings = context.getSendMessageSettings()
if (address != null) { if (address != null) {
var subscriptionId: Int? = null
val availableSIMs = context.subscriptionManagerCompat().activeSubscriptionInfoList val availableSIMs = context.subscriptionManagerCompat().activeSubscriptionInfoList
if ((availableSIMs?.size ?: 0) > 1) { if ((availableSIMs?.size ?: 0) > 1) {
val currentSIMCardIndex = context.config.getUseSIMIdAtNumber(address) val currentSIMCardIndex = context.config.getUseSIMIdAtNumber(address)
val wantedId = availableSIMs.getOrNull(currentSIMCardIndex) val wantedId = availableSIMs.getOrNull(currentSIMCardIndex)
if (wantedId != null) { if (wantedId != null) {
settings.subscriptionId = wantedId.subscriptionId subscriptionId = wantedId.subscriptionId
} }
} }
}
val transaction = Transaction(context, settings)
val message = com.klinker.android.send_message.Message(msg, address)
ensureBackgroundThread { ensureBackgroundThread {
try { try {
val smsSentIntent = Intent(context, SmsStatusSentReceiver::class.java) context.sendMessage(body, listOf(address), subscriptionId, emptyList())
val deliveredIntent = Intent(context, SmsStatusDeliveredReceiver::class.java) val message = context.getMessages(threadId, getImageResolutions = false, includeScheduledMessages = false, limit = 1).lastOrNull()
if (message != null) {
transaction.setExplicitBroadcastForSentSms(smsSentIntent) context.messagesDB.insertOrUpdate(message)
transaction.setExplicitBroadcastForDeliveredSms(deliveredIntent) }
transaction.sendNewMessage(message)
} catch (e: Exception) { } catch (e: Exception) {
context.showErrorToast(e) context.showErrorToast(e)
} }
context.notificationManager.cancel(threadId.hashCode()) val photoUri = SimpleContactsHelper(context).getPhotoUriFromPhoneNumber(address)
val bitmap = context.getNotificationBitmap(photoUri)
Handler(Looper.getMainLooper()).post {
context.notificationHelper.showMessageNotification(address, body, threadId, bitmap, sender = null, alertOnlyOnce = true)
}
context.markThreadMessagesRead(threadId) context.markThreadMessagesRead(threadId)
context.conversationsDB.markRead(threadId) context.conversationsDB.markRead(threadId)
} }
} }
}
} }

View File

@ -3,13 +3,9 @@ package com.simplemobiletools.smsmessenger.receivers
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.provider.Telephony import android.provider.Telephony
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.simplemobiletools.commons.extensions.baseConfig import com.simplemobiletools.commons.extensions.baseConfig
import com.simplemobiletools.commons.extensions.getMyContactsCursor import com.simplemobiletools.commons.extensions.getMyContactsCursor
import com.simplemobiletools.commons.extensions.isNumberBlocked import com.simplemobiletools.commons.extensions.isNumberBlocked
@ -17,7 +13,6 @@ import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.models.PhoneNumber import com.simplemobiletools.commons.models.PhoneNumber
import com.simplemobiletools.commons.models.SimpleContact import com.simplemobiletools.commons.models.SimpleContact
import com.simplemobiletools.smsmessenger.R
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.models.Message import com.simplemobiletools.smsmessenger.models.Message
@ -63,7 +58,7 @@ class SmsReceiver : BroadcastReceiver() {
context: Context, address: String, subject: String, body: String, date: Long, read: Int, threadId: Long, type: Int, subscriptionId: Int, status: Int context: Context, address: String, subject: String, body: String, date: Long, read: Int, threadId: Long, type: Int, subscriptionId: Int, status: Int
) { ) {
val photoUri = SimpleContactsHelper(context).getPhotoUriFromPhoneNumber(address) val photoUri = SimpleContactsHelper(context).getPhotoUriFromPhoneNumber(address)
val bitmap = getPhotoForNotification(photoUri, context) val bitmap = context.getNotificationBitmap(photoUri)
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
if (!context.isNumberBlocked(address)) { if (!context.isNumberBlocked(address)) {
val privateCursor = context.getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true) val privateCursor = context.getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true)
@ -97,27 +92,4 @@ class SmsReceiver : BroadcastReceiver() {
} }
} }
} }
private fun getPhotoForNotification(photoUri: String, context: Context): Bitmap? {
val size = context.resources.getDimension(R.dimen.notification_large_icon_size).toInt()
if (photoUri.isEmpty()) {
return null
}
val options = RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
return try {
Glide.with(context)
.asBitmap()
.load(photoUri)
.apply(options)
.apply(RequestOptions.circleCropTransform())
.into(size, size)
.get()
} catch (e: Exception) {
null
}
}
} }

View File

@ -1,31 +1,18 @@
package com.simplemobiletools.smsmessenger.receivers package com.simplemobiletools.smsmessenger.receivers
import android.annotation.SuppressLint import android.app.Activity
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.media.AudioAttributes
import android.media.AudioManager
import android.media.RingtoneManager
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
import androidx.core.app.NotificationCompat import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ProcessLifecycleOwner
import com.klinker.android.send_message.SentReceiver import com.klinker.android.send_message.SentReceiver
import com.simplemobiletools.commons.extensions.getMyContactsCursor import com.simplemobiletools.commons.extensions.getMyContactsCursor
import com.simplemobiletools.commons.extensions.getProperPrimaryColor
import com.simplemobiletools.commons.helpers.SimpleContactsHelper
import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.helpers.isOreoPlus
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.ThreadActivity
import com.simplemobiletools.smsmessenger.extensions.* import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.NOTIFICATION_CHANNEL
import com.simplemobiletools.smsmessenger.helpers.THREAD_ID
import com.simplemobiletools.smsmessenger.helpers.refreshMessages import com.simplemobiletools.smsmessenger.helpers.refreshMessages
class SmsStatusSentReceiver : SentReceiver() { class SmsStatusSentReceiver : SentReceiver() {
@ -35,11 +22,11 @@ class SmsStatusSentReceiver : SentReceiver() {
val uri = Uri.parse(intent.getStringExtra("message_uri")) val uri = Uri.parse(intent.getStringExtra("message_uri"))
val messageId = uri?.lastPathSegment?.toLong() ?: 0L val messageId = uri?.lastPathSegment?.toLong() ?: 0L
ensureBackgroundThread { ensureBackgroundThread {
val type = if (intent.extras!!.containsKey("errorCode")) { val type = if (receiverResultCode == Activity.RESULT_OK) {
Telephony.Sms.MESSAGE_TYPE_SENT
} else {
showSendingFailedNotification(context, messageId) showSendingFailedNotification(context, messageId)
Telephony.Sms.MESSAGE_TYPE_FAILED Telephony.Sms.MESSAGE_TYPE_FAILED
} else {
Telephony.Sms.MESSAGE_TYPE_SENT
} }
context.updateMessageType(messageId, type) context.updateMessageType(messageId, type)
@ -59,61 +46,16 @@ class SmsStatusSentReceiver : SentReceiver() {
private fun showSendingFailedNotification(context: Context, messageId: Long) { private fun showSendingFailedNotification(context: Context, messageId: Long) {
Handler(Looper.getMainLooper()).post { Handler(Looper.getMainLooper()).post {
val privateCursor = context.getMyContactsCursor(false, true) if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
return@post
}
val privateCursor = context.getMyContactsCursor(favoritesOnly = false, withPhoneNumbersOnly = true)
ensureBackgroundThread { ensureBackgroundThread {
val address = context.getMessageRecipientAddress(messageId) val address = context.getMessageRecipientAddress(messageId)
val threadId = context.getThreadId(address) val threadId = context.getThreadId(address)
val senderName = context.getNameFromAddress(address, privateCursor) val recipientName = context.getNameFromAddress(address, privateCursor)
showNotification(context, senderName, threadId) context.notificationHelper.showSendingFailedNotification(recipientName, threadId)
} }
} }
} }
@SuppressLint("NewApi")
private fun showNotification(context: Context, recipientName: String, threadId: Long) {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
if (isOreoPlus()) {
val audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
.build()
val name = context.getString(R.string.message_not_sent_short)
val importance = NotificationManager.IMPORTANCE_HIGH
NotificationChannel(NOTIFICATION_CHANNEL, name, importance).apply {
setBypassDnd(false)
enableLights(true)
setSound(soundUri, audioAttributes)
enableVibration(true)
notificationManager.createNotificationChannel(this)
}
}
val intent = Intent(context, ThreadActivity::class.java).apply {
putExtra(THREAD_ID, threadId)
}
val pendingIntent = PendingIntent.getActivity(context, threadId.hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
val summaryText = String.format(context.getString(R.string.message_sending_error), recipientName)
val largeIcon = SimpleContactsHelper(context).getContactLetterIcon(recipientName)
val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL)
.setContentTitle(context.getString(R.string.message_not_sent_short))
.setContentText(summaryText)
.setColor(context.getProperPrimaryColor())
.setSmallIcon(R.drawable.ic_messenger)
.setLargeIcon(largeIcon)
.setStyle(NotificationCompat.BigTextStyle().bigText(summaryText))
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_MAX)
.setDefaults(Notification.DEFAULT_LIGHTS)
.setCategory(Notification.CATEGORY_MESSAGE)
.setAutoCancel(true)
.setSound(soundUri, AudioManager.STREAM_NOTIFICATION)
.setChannelId(NOTIFICATION_CHANNEL)
notificationManager.notify(threadId.hashCode(), builder.build())
}
} }