mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 20:48:43 +00:00
Squelch notifications in noisy groups and during large initial message processing.
This commit is contained in:
committed by
Greyson Parrelli
parent
6b6e2490e7
commit
1b82d10b39
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user