diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java index 235d660f7c..f452f4fe7a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/OptimizedMessageNotifier.java @@ -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); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt index 3266748983..6415773a68 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/DefaultMessageNotifier.kt @@ -61,12 +61,16 @@ class DefaultMessageNotifier(context: Application) : MessageNotifier { private val threadReminders: MutableMap = ConcurrentHashMap() private val stickyThreads: MutableMap = mutableMapOf() + private val lastThreadNotification: MutableMap = 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 { @@ -209,7 +213,8 @@ class DefaultMessageNotifier(context: Application) : MessageNotifier { lastAudibleNotification = lastAudibleNotification, notificationConfigurationChanged = notificationConfigurationChanged, alertOverrides = alertOverrides, - previousState = previousState + previousState = previousState, + lastThreadNotification = lastThreadNotification ) previousState = state diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt index 892b12ae7c..f147f1a09a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationFactory.kt @@ -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, - previousState: NotificationState + previousState: NotificationState, + lastThreadNotification: MutableMap ): Set { 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, - nonVisibleThreadCount: Int + nonVisibleThreadCount: Int, + lastThreadNotification: MutableMap ): Set { val threadsThatNewlyAlerted: MutableSet = 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, nonVisibleThreadCount: Int, - previousState: NotificationState + previousState: NotificationState, + lastThreadNotification: MutableMap ): Set { val threadsThatNewlyAlerted: MutableSet = 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, diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItem.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItem.kt index daaa84c425..0740222222 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItem.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/v2/NotificationItem.kt @@ -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