Squelch notifications in noisy groups and during large initial message processing.

This commit is contained in:
Cody Henthorne
2023-05-12 11:30:04 -04:00
committed by Greyson Parrelli
parent 6b6e2490e7
commit 1b82d10b39
4 changed files with 52 additions and 9 deletions

View File

@@ -29,7 +29,7 @@ public class OptimizedMessageNotifier implements MessageNotifier {
@MainThread
public OptimizedMessageNotifier(@NonNull Application context) {
this.limiter = new LeakyBucketLimiter(5, 1000, new Handler(SignalExecutors.getAndStartHandlerThread("signal-notifier", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD).getLooper()));
this.limiter = new LeakyBucketLimiter(3, 1000, new Handler(SignalExecutors.getAndStartHandlerThread("signal-notifier", ThreadUtil.PRIORITY_IMPORTANT_BACKGROUND_THREAD).getLooper()));
this.defaultMessageNotifier = new DefaultMessageNotifier(context);
}

View File

@@ -61,12 +61,16 @@ class DefaultMessageNotifier(context: Application) : MessageNotifier {
private val threadReminders: MutableMap<ConversationId, Reminder> = ConcurrentHashMap()
private val stickyThreads: MutableMap<ConversationId, StickyThread> = mutableMapOf()
private val lastThreadNotification: MutableMap<ConversationId, Long> = ConcurrentHashMap()
private val executor = CancelableExecutor()
override fun setVisibleThread(conversationId: ConversationId?) {
visibleThread = conversationId
stickyThreads.remove(conversationId)
if (conversationId != null) {
lastThreadNotification.remove(conversationId)
}
}
override fun getVisibleThread(): Optional<ConversationId> {
@@ -209,7 +213,8 @@ class DefaultMessageNotifier(context: Application) : MessageNotifier {
lastAudibleNotification = lastAudibleNotification,
notificationConfigurationChanged = notificationConfigurationChanged,
alertOverrides = alertOverrides,
previousState = previousState
previousState = previousState,
lastThreadNotification = lastThreadNotification
)
previousState = state

View File

@@ -25,6 +25,7 @@ import org.thoughtcrime.securesms.conversation.ConversationIntents
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.NotificationIds
@@ -34,6 +35,8 @@ import org.thoughtcrime.securesms.util.BubbleUtil
import org.thoughtcrime.securesms.util.ConversationUtil
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
/**
* Given a notification state consisting of conversations of messages, show appropriate system notifications.
@@ -42,6 +45,9 @@ object NotificationFactory {
val TAG: String = Log.tag(NotificationFactory::class.java)
private val STILL_DECRYPTING_INDIVIDUAL_THROTTLE: Duration = 5.seconds
private val GROUP_THROTTLE: Duration = 20.seconds
fun notify(
context: Context,
state: NotificationState,
@@ -51,7 +57,8 @@ object NotificationFactory {
lastAudibleNotification: Long,
notificationConfigurationChanged: Boolean,
alertOverrides: Set<ConversationId>,
previousState: NotificationState
previousState: NotificationState,
lastThreadNotification: MutableMap<ConversationId, Long>
): Set<ConversationId> {
if (state.isEmpty) {
Log.d(TAG, "State is empty, bailing")
@@ -68,7 +75,8 @@ object NotificationFactory {
defaultBubbleState = defaultBubbleState,
lastAudibleNotification = lastAudibleNotification,
alertOverrides = alertOverrides,
nonVisibleThreadCount = nonVisibleThreadCount
nonVisibleThreadCount = nonVisibleThreadCount,
lastThreadNotification = lastThreadNotification
)
} else {
notify24(
@@ -81,7 +89,8 @@ object NotificationFactory {
notificationConfigurationChanged = notificationConfigurationChanged,
alertOverrides = alertOverrides,
nonVisibleThreadCount = nonVisibleThreadCount,
previousState = previousState
previousState = previousState,
lastThreadNotification = lastThreadNotification
)
}
}
@@ -94,7 +103,8 @@ object NotificationFactory {
defaultBubbleState: BubbleUtil.BubbleState,
lastAudibleNotification: Long,
alertOverrides: Set<ConversationId>,
nonVisibleThreadCount: Int
nonVisibleThreadCount: Int,
lastThreadNotification: MutableMap<ConversationId, Long>
): Set<ConversationId> {
val threadsThatNewlyAlerted: MutableSet<ConversationId> = mutableSetOf()
@@ -107,12 +117,17 @@ object NotificationFactory {
if (nonVisibleThreadCount == 1) {
state.conversations.first { it.thread != visibleThread }.let { conversation ->
val shouldAlert = shouldAlert(conversation, lastThreadNotification.getOrDefault(conversation.thread, 0), alertOverrides.contains(conversation.thread))
if (shouldAlert) {
lastThreadNotification[conversation.thread] = System.currentTimeMillis()
}
notifyForConversation(
context = context,
conversation = conversation,
targetThread = targetThread,
defaultBubbleState = defaultBubbleState,
shouldAlert = (conversation.hasNewNotifications() || alertOverrides.contains(conversation.thread)) && !conversation.mostRecentNotification.authorRecipient.isSelf
shouldAlert = shouldAlert
)
if (conversation.hasNewNotifications()) {
threadsThatNewlyAlerted += conversation.thread
@@ -138,7 +153,8 @@ object NotificationFactory {
notificationConfigurationChanged: Boolean,
alertOverrides: Set<ConversationId>,
nonVisibleThreadCount: Int,
previousState: NotificationState
previousState: NotificationState,
lastThreadNotification: MutableMap<ConversationId, Long>
): Set<ConversationId> {
val threadsThatNewlyAlerted: MutableSet<ConversationId> = mutableSetOf()
@@ -152,12 +168,22 @@ object NotificationFactory {
}
try {
val shouldAlert = shouldAlert(
conversation = conversation,
lastNotificationTimestamp = lastThreadNotification.getOrDefault(conversation.thread, 0),
alertOverride = alertOverrides.contains(conversation.thread)
)
if (shouldAlert) {
lastThreadNotification[conversation.thread] = System.currentTimeMillis()
}
notifyForConversation(
context = context,
conversation = conversation,
targetThread = targetThread,
defaultBubbleState = defaultBubbleState,
shouldAlert = (conversation.hasNewNotifications() || alertOverrides.contains(conversation.thread)) && !conversation.mostRecentNotification.authorRecipient.isSelf
shouldAlert = shouldAlert
)
} catch (e: SecurityException) {
Log.w(TAG, "Too many pending intents device quirk", e)
@@ -172,6 +198,17 @@ object NotificationFactory {
return threadsThatNewlyAlerted
}
private fun shouldAlert(conversation: NotificationConversation, lastNotificationTimestamp: Long, alertOverride: Boolean): Boolean {
val throttle: Duration = when {
conversation.recipient.isGroup && (conversation.mostRecentNotification as? MessageNotification)?.hasSelfMention == false -> GROUP_THROTTLE
ApplicationDependencies.getIncomingMessageObserver().decryptionDrained -> STILL_DECRYPTING_INDIVIDUAL_THROTTLE
else -> 0.seconds
}
val canAlertBasedOnTime: Boolean = lastNotificationTimestamp < System.currentTimeMillis() - throttle.inWholeMilliseconds || lastNotificationTimestamp > System.currentTimeMillis()
return ((conversation.hasNewNotifications() && canAlertBasedOnTime) || alertOverride) && !conversation.mostRecentNotification.authorRecipient.isSelf
}
private fun notifyForConversation(
context: Context,
conversation: NotificationConversation,

View File

@@ -205,6 +205,7 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : N
override val timestamp: Long = record.timestamp
override val authorRecipient: Recipient = record.fromRecipient.resolve()
override val isNewNotification: Boolean = notifiedTimestamp == 0L
val hasSelfMention = record.hasSelfMention()
private var thumbnailInfo: ThumbnailInfo? = null