kopia lustrzana https://github.com/ryukoposting/Signal-Android
255 wiersze
10 KiB
Kotlin
255 wiersze
10 KiB
Kotlin
package org.thoughtcrime.securesms.notifications.v2
|
|
|
|
import android.app.Notification
|
|
import android.app.NotificationManager
|
|
import android.app.PendingIntent
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.graphics.BitmapFactory
|
|
import android.media.AudioAttributes
|
|
import android.media.AudioManager
|
|
import android.media.RingtoneManager
|
|
import android.net.Uri
|
|
import android.os.Build
|
|
import android.os.TransactionTooLargeException
|
|
import androidx.core.app.NotificationCompat
|
|
import androidx.core.app.NotificationManagerCompat
|
|
import androidx.core.content.ContextCompat
|
|
import org.signal.core.util.concurrent.SignalExecutors
|
|
import org.signal.core.util.logging.Log
|
|
import org.thoughtcrime.securesms.MainActivity
|
|
import org.thoughtcrime.securesms.R
|
|
import org.thoughtcrime.securesms.conversation.ConversationIntents
|
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
|
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier
|
|
import org.thoughtcrime.securesms.notifications.NotificationChannels
|
|
import org.thoughtcrime.securesms.notifications.NotificationIds
|
|
import org.thoughtcrime.securesms.recipients.Recipient
|
|
import org.thoughtcrime.securesms.util.BubbleUtil
|
|
import org.thoughtcrime.securesms.util.ConversationUtil
|
|
import org.thoughtcrime.securesms.util.FeatureFlags
|
|
import org.thoughtcrime.securesms.util.ServiceUtil
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
|
|
|
private val TAG = Log.tag(NotificationFactory::class.java)
|
|
|
|
/**
|
|
* Given a notification state consisting of conversations of messages, show appropriate system notifications.
|
|
*/
|
|
object NotificationFactory {
|
|
|
|
fun notify(
|
|
context: Context,
|
|
state: NotificationStateV2,
|
|
visibleThreadId: Long,
|
|
targetThreadId: Long,
|
|
defaultBubbleState: BubbleUtil.BubbleState,
|
|
lastAudibleNotification: Long,
|
|
alertOverrides: Set<Long>
|
|
): Set<Long> {
|
|
if (state.isEmpty) {
|
|
Log.d(TAG, "State is empty, bailing")
|
|
return emptySet()
|
|
}
|
|
val threadsThatNewlyAlerted: MutableSet<Long> = mutableSetOf()
|
|
|
|
if (Build.VERSION.SDK_INT >= 23 || state.conversations.size == 1) {
|
|
state.conversations.forEach { conversation ->
|
|
if (conversation.threadId == visibleThreadId && conversation.hasNewNotifications()) {
|
|
notifyInThread(context, conversation.recipient, lastAudibleNotification)
|
|
} else if (conversation.hasNewNotifications() || alertOverrides.contains(conversation.threadId)) {
|
|
|
|
if (conversation.hasNewNotifications()) {
|
|
threadsThatNewlyAlerted += conversation.threadId
|
|
}
|
|
|
|
notifyForConversation(
|
|
context = context,
|
|
conversation = conversation,
|
|
recipient = conversation.recipient,
|
|
targetThreadId = targetThreadId,
|
|
defaultBubbleState = defaultBubbleState
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
if (state.conversations.size > 1 || ServiceUtil.getNotificationManager(context).isDisplayingSummaryNotification()) {
|
|
val builder: NotificationBuilder = NotificationBuilder.create(context)
|
|
|
|
builder.apply {
|
|
setSmallIcon(R.drawable.ic_notification)
|
|
setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
|
|
setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
|
setGroup(DefaultMessageNotifier.NOTIFICATION_GROUP)
|
|
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
|
setChannelId(NotificationChannels.getMessagesChannel(context))
|
|
setContentTitle(context.getString(R.string.app_name))
|
|
setContentIntent(PendingIntent.getActivity(context, 0, MainActivity.clearTop(context), 0))
|
|
setGroupSummary(true)
|
|
setSubText(context.getString(R.string.MessageNotifier_d_new_messages_in_d_conversations, state.messageCount, state.threadCount))
|
|
setContentInfo(state.messageCount.toString())
|
|
setNumber(state.messageCount)
|
|
setSummaryContentText(state.mostRecentSender)
|
|
setDeleteIntent(state.getDeleteIntent(context))
|
|
setWhen(state.mostRecentNotification)
|
|
addMarkAsReadAction(state)
|
|
addMessages(state)
|
|
setOnlyAlertOnce(!state.notificationItems.any { it.isNewNotification })
|
|
setPriority(TextSecurePreferences.getNotificationPriority(context))
|
|
setLights()
|
|
setAlarms(state.mostRecentSender)
|
|
setTicker(state.mostRecentNotification.getStyledPrimaryText(context, true))
|
|
}
|
|
|
|
Log.d(TAG, "showing summary notification")
|
|
NotificationManagerCompat.from(context).safelyNotify(context, null, NotificationIds.MESSAGE_SUMMARY, builder.build())
|
|
}
|
|
|
|
return threadsThatNewlyAlerted
|
|
}
|
|
|
|
private fun notifyForConversation(
|
|
context: Context,
|
|
conversation: NotificationConversation,
|
|
recipient: Recipient,
|
|
targetThreadId: Long,
|
|
defaultBubbleState: BubbleUtil.BubbleState
|
|
) {
|
|
val builder: NotificationBuilder = NotificationBuilder.create(context)
|
|
|
|
builder.apply {
|
|
setSmallIcon(R.drawable.ic_notification)
|
|
setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
|
|
setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
|
setGroup(DefaultMessageNotifier.NOTIFICATION_GROUP)
|
|
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
|
setChannelId(recipient.notificationChannel ?: NotificationChannels.getMessagesChannel(context))
|
|
setContentTitle(conversation.getContentTitle(context))
|
|
setLargeIcon(conversation.getLargeIcon(context))
|
|
addPerson(recipient)
|
|
setShortcutId(ConversationUtil.getShortcutId(recipient))
|
|
setContentInfo(conversation.messageCount.toString())
|
|
setNumber(conversation.messageCount)
|
|
setContentText(conversation.getContentText(context))
|
|
setContentIntent(conversation.getPendingIntent(context))
|
|
setDeleteIntent(conversation.getDeleteIntent(context))
|
|
setSortKey(conversation.sortKey.toString())
|
|
setWhen(conversation)
|
|
addReplyActions(conversation)
|
|
setOnlyAlertOnce(false)
|
|
addMessages(conversation)
|
|
setPriority(TextSecurePreferences.getNotificationPriority(context))
|
|
setLights()
|
|
setAlarms(conversation.recipient)
|
|
setTicker(conversation.mostRecentNotification.getStyledPrimaryText(context, true))
|
|
setBubbleMetadata(conversation, if (targetThreadId == conversation.threadId) defaultBubbleState else BubbleUtil.BubbleState.HIDDEN)
|
|
}
|
|
|
|
if (conversation.messageCount == 1 && conversation.mostRecentNotification.isJoined) {
|
|
builder.addTurnOffJoinedNotificationsAction(conversation.getTurnOffJoinedNotificationsIntent(context))
|
|
}
|
|
|
|
NotificationManagerCompat.from(context).safelyNotify(context, conversation.recipient, conversation.notificationId, builder.build())
|
|
}
|
|
|
|
private fun notifyInThread(context: Context, recipient: Recipient, lastAudibleNotification: Long) {
|
|
if (!TextSecurePreferences.isInThreadNotifications(context) ||
|
|
ServiceUtil.getAudioManager(context).ringerMode != AudioManager.RINGER_MODE_NORMAL ||
|
|
(System.currentTimeMillis() - lastAudibleNotification) < DefaultMessageNotifier.MIN_AUDIBLE_PERIOD_MILLIS
|
|
) {
|
|
return
|
|
}
|
|
|
|
val uri: Uri = if (NotificationChannels.supported()) {
|
|
NotificationChannels.getMessageRingtone(context, recipient) ?: NotificationChannels.getMessageRingtone(context)
|
|
} else {
|
|
recipient.messageRingtone ?: TextSecurePreferences.getNotificationRingtone(context)
|
|
}
|
|
|
|
if (uri.toString().isEmpty()) {
|
|
Log.d(TAG, "ringtone uri is empty")
|
|
return
|
|
}
|
|
|
|
val ringtone = RingtoneManager.getRingtone(context, uri)
|
|
|
|
if (ringtone == null) {
|
|
Log.w(TAG, "ringtone is null")
|
|
return
|
|
}
|
|
|
|
if (Build.VERSION.SDK_INT >= 21) {
|
|
ringtone.audioAttributes = AudioAttributes.Builder()
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
|
|
.setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
|
|
.build()
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
ringtone.streamType = AudioManager.STREAM_NOTIFICATION
|
|
}
|
|
|
|
ringtone.play()
|
|
}
|
|
|
|
fun notifyMessageDeliveryFailed(context: Context, recipient: Recipient, threadId: Long, visibleThread: Long) {
|
|
if (threadId == visibleThread) {
|
|
notifyInThread(context, recipient, 0)
|
|
return
|
|
}
|
|
|
|
val intent: Intent = ConversationIntents.createBuilder(context, recipient.id, threadId)
|
|
.build()
|
|
.makeUniqueToPreventMerging()
|
|
|
|
val builder: NotificationBuilder = NotificationBuilder.create(context)
|
|
|
|
builder.apply {
|
|
setSmallIcon(R.drawable.ic_notification)
|
|
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.ic_action_warning_red))
|
|
setContentTitle(context.getString(R.string.MessageNotifier_message_delivery_failed))
|
|
setContentText(context.getString(R.string.MessageNotifier_failed_to_deliver_message))
|
|
setTicker(context.getString(R.string.MessageNotifier_error_delivering_message))
|
|
setContentIntent(PendingIntent.getActivity(context, 0, intent, 0))
|
|
setAutoCancel(true)
|
|
setAlarms(recipient)
|
|
setChannelId(NotificationChannels.FAILURES)
|
|
}
|
|
|
|
NotificationManagerCompat.from(context).safelyNotify(context, recipient, threadId.toInt(), builder.build())
|
|
}
|
|
|
|
private fun NotificationManager.isDisplayingSummaryNotification(): Boolean {
|
|
if (Build.VERSION.SDK_INT >= 23) {
|
|
try {
|
|
return activeNotifications.any { notification -> notification.id == NotificationIds.MESSAGE_SUMMARY }
|
|
} catch (e: Throwable) {
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
private fun NotificationManagerCompat.safelyNotify(context: Context, threadRecipient: Recipient?, notificationId: Int, notification: Notification) {
|
|
try {
|
|
notify(notificationId, notification)
|
|
if (FeatureFlags.internalUser()) {
|
|
Log.i(TAG, "Posted notification: $notification")
|
|
}
|
|
} catch (e: SecurityException) {
|
|
Log.i(TAG, "Security exception when posting notification, clearing ringtone")
|
|
if (threadRecipient != null) {
|
|
SignalExecutors.BOUNDED.execute {
|
|
DatabaseFactory.getRecipientDatabase(context).setMessageRingtone(threadRecipient.id, null)
|
|
NotificationChannels.updateMessageRingtone(context, threadRecipient, null)
|
|
}
|
|
}
|
|
} catch (runtimeException: RuntimeException) {
|
|
if (runtimeException.cause is TransactionTooLargeException) {
|
|
Log.e(TAG, "Transaction too large", runtimeException)
|
|
} else {
|
|
throw runtimeException
|
|
}
|
|
}
|
|
}
|
|
}
|