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 @MainThread
public OptimizedMessageNotifier(@NonNull Application context) { 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); 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 threadReminders: MutableMap<ConversationId, Reminder> = ConcurrentHashMap()
private val stickyThreads: MutableMap<ConversationId, StickyThread> = mutableMapOf() private val stickyThreads: MutableMap<ConversationId, StickyThread> = mutableMapOf()
private val lastThreadNotification: MutableMap<ConversationId, Long> = ConcurrentHashMap()
private val executor = CancelableExecutor() private val executor = CancelableExecutor()
override fun setVisibleThread(conversationId: ConversationId?) { override fun setVisibleThread(conversationId: ConversationId?) {
visibleThread = conversationId visibleThread = conversationId
stickyThreads.remove(conversationId) stickyThreads.remove(conversationId)
if (conversationId != null) {
lastThreadNotification.remove(conversationId)
}
} }
override fun getVisibleThread(): Optional<ConversationId> { override fun getVisibleThread(): Optional<ConversationId> {
@@ -209,7 +213,8 @@ class DefaultMessageNotifier(context: Application) : MessageNotifier {
lastAudibleNotification = lastAudibleNotification, lastAudibleNotification = lastAudibleNotification,
notificationConfigurationChanged = notificationConfigurationChanged, notificationConfigurationChanged = notificationConfigurationChanged,
alertOverrides = alertOverrides, alertOverrides = alertOverrides,
previousState = previousState previousState = previousState,
lastThreadNotification = lastThreadNotification
) )
previousState = state 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.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.NotificationIds 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.ConversationUtil
import org.thoughtcrime.securesms.util.ServiceUtil import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.TextSecurePreferences 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. * 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) 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( fun notify(
context: Context, context: Context,
state: NotificationState, state: NotificationState,
@@ -51,7 +57,8 @@ object NotificationFactory {
lastAudibleNotification: Long, lastAudibleNotification: Long,
notificationConfigurationChanged: Boolean, notificationConfigurationChanged: Boolean,
alertOverrides: Set<ConversationId>, alertOverrides: Set<ConversationId>,
previousState: NotificationState previousState: NotificationState,
lastThreadNotification: MutableMap<ConversationId, Long>
): Set<ConversationId> { ): Set<ConversationId> {
if (state.isEmpty) { if (state.isEmpty) {
Log.d(TAG, "State is empty, bailing") Log.d(TAG, "State is empty, bailing")
@@ -68,7 +75,8 @@ object NotificationFactory {
defaultBubbleState = defaultBubbleState, defaultBubbleState = defaultBubbleState,
lastAudibleNotification = lastAudibleNotification, lastAudibleNotification = lastAudibleNotification,
alertOverrides = alertOverrides, alertOverrides = alertOverrides,
nonVisibleThreadCount = nonVisibleThreadCount nonVisibleThreadCount = nonVisibleThreadCount,
lastThreadNotification = lastThreadNotification
) )
} else { } else {
notify24( notify24(
@@ -81,7 +89,8 @@ object NotificationFactory {
notificationConfigurationChanged = notificationConfigurationChanged, notificationConfigurationChanged = notificationConfigurationChanged,
alertOverrides = alertOverrides, alertOverrides = alertOverrides,
nonVisibleThreadCount = nonVisibleThreadCount, nonVisibleThreadCount = nonVisibleThreadCount,
previousState = previousState previousState = previousState,
lastThreadNotification = lastThreadNotification
) )
} }
} }
@@ -94,7 +103,8 @@ object NotificationFactory {
defaultBubbleState: BubbleUtil.BubbleState, defaultBubbleState: BubbleUtil.BubbleState,
lastAudibleNotification: Long, lastAudibleNotification: Long,
alertOverrides: Set<ConversationId>, alertOverrides: Set<ConversationId>,
nonVisibleThreadCount: Int nonVisibleThreadCount: Int,
lastThreadNotification: MutableMap<ConversationId, Long>
): Set<ConversationId> { ): Set<ConversationId> {
val threadsThatNewlyAlerted: MutableSet<ConversationId> = mutableSetOf() val threadsThatNewlyAlerted: MutableSet<ConversationId> = mutableSetOf()
@@ -107,12 +117,17 @@ object NotificationFactory {
if (nonVisibleThreadCount == 1) { if (nonVisibleThreadCount == 1) {
state.conversations.first { it.thread != visibleThread }.let { conversation -> 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( notifyForConversation(
context = context, context = context,
conversation = conversation, conversation = conversation,
targetThread = targetThread, targetThread = targetThread,
defaultBubbleState = defaultBubbleState, defaultBubbleState = defaultBubbleState,
shouldAlert = (conversation.hasNewNotifications() || alertOverrides.contains(conversation.thread)) && !conversation.mostRecentNotification.authorRecipient.isSelf shouldAlert = shouldAlert
) )
if (conversation.hasNewNotifications()) { if (conversation.hasNewNotifications()) {
threadsThatNewlyAlerted += conversation.thread threadsThatNewlyAlerted += conversation.thread
@@ -138,7 +153,8 @@ object NotificationFactory {
notificationConfigurationChanged: Boolean, notificationConfigurationChanged: Boolean,
alertOverrides: Set<ConversationId>, alertOverrides: Set<ConversationId>,
nonVisibleThreadCount: Int, nonVisibleThreadCount: Int,
previousState: NotificationState previousState: NotificationState,
lastThreadNotification: MutableMap<ConversationId, Long>
): Set<ConversationId> { ): Set<ConversationId> {
val threadsThatNewlyAlerted: MutableSet<ConversationId> = mutableSetOf() val threadsThatNewlyAlerted: MutableSet<ConversationId> = mutableSetOf()
@@ -152,12 +168,22 @@ object NotificationFactory {
} }
try { 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( notifyForConversation(
context = context, context = context,
conversation = conversation, conversation = conversation,
targetThread = targetThread, targetThread = targetThread,
defaultBubbleState = defaultBubbleState, defaultBubbleState = defaultBubbleState,
shouldAlert = (conversation.hasNewNotifications() || alertOverrides.contains(conversation.thread)) && !conversation.mostRecentNotification.authorRecipient.isSelf shouldAlert = shouldAlert
) )
} catch (e: SecurityException) { } catch (e: SecurityException) {
Log.w(TAG, "Too many pending intents device quirk", e) Log.w(TAG, "Too many pending intents device quirk", e)
@@ -172,6 +198,17 @@ object NotificationFactory {
return threadsThatNewlyAlerted 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( private fun notifyForConversation(
context: Context, context: Context,
conversation: NotificationConversation, conversation: NotificationConversation,

View File

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