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 { 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() } } }