From 5a5741f47bdb6135479b0314535c7b412b8fde73 Mon Sep 17 00:00:00 2001 From: Ryan Zhao Date: Tue, 23 Nov 2021 11:05:04 +1100 Subject: [PATCH] Group notifications from background polling --- Session/Notifications/AppNotifications.swift | 13 +++++--- .../UserNotificationsAdaptee.swift | 33 ++++++++++++++----- .../MessageReceiver+Handling.swift | 4 ++- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index 4b3b9f04b..fc80e8a71 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -37,6 +37,7 @@ struct AppNotificationUserInfoKey { static let threadId = "Signal.AppNotificationsUserInfoKey.threadId" static let callBackNumber = "Signal.AppNotificationsUserInfoKey.callBackNumber" static let localCallId = "Signal.AppNotificationsUserInfoKey.localCallId" + static let threadNotificationCounter = "Session.AppNotificationsUserInfoKey.threadNotificationCounter" } extension AppNotificationCategory { @@ -80,9 +81,9 @@ extension AppNotificationAction { } } -// Delay notification of incoming messages when it's likely to be read by a linked device to -// avoid notifying a user on their phone while a conversation is actively happening on desktop. -let kNotificationDelayForRemoteRead: TimeInterval = 5 +// Delay notification of incoming messages when it's a background polling to +// avoid too many notifications fired at the same time +let kNotificationDelayForBackgroumdPoll: TimeInterval = 5 let kAudioNotificationsThrottleCount = 2 let kAudioNotificationsThrottleInterval: TimeInterval = 5 @@ -201,6 +202,8 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { owsFailDebug("unexpected thread: \(thread)") return } + default: + notificationTitle = "Session" } var notificationBody: String? @@ -209,6 +212,8 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { notificationBody = NotificationStrings.incomingMessageBody case .namePreview: notificationBody = messageText + default: + notificationBody = NotificationStrings.incomingMessageBody } guard let threadId = thread.uniqueId else { @@ -220,7 +225,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { // Don't reply from lockscreen if anyone in this conversation is // "no longer verified". - var category = AppNotificationCategory.incomingMessage + let category = AppNotificationCategory.incomingMessage let userInfo = [ AppNotificationUserInfoKey.threadId: threadId diff --git a/Session/Notifications/UserNotificationsAdaptee.swift b/Session/Notifications/UserNotificationsAdaptee.swift index 13c648fe6..551224aaf 100644 --- a/Session/Notifications/UserNotificationsAdaptee.swift +++ b/Session/Notifications/UserNotificationsAdaptee.swift @@ -96,17 +96,18 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee { let content = UNMutableNotificationContent() content.categoryIdentifier = category.identifier content.userInfo = userInfo + let isReplacingNotification = replacingIdentifier != nil + var isBackgroudPoll = false + if let threadIdentifier = userInfo[AppNotificationUserInfoKey.threadId] as? String { + content.threadIdentifier = threadIdentifier + isBackgroudPoll = replacingIdentifier == threadIdentifier + } let isAppActive = UIApplication.shared.applicationState == .active if let sound = sound, sound != OWSSound.none { content.sound = sound.notificationSound(isQuiet: isAppActive) } - - var notificationIdentifier: String = UUID().uuidString - if let replacingIdentifier = replacingIdentifier { - notificationIdentifier = replacingIdentifier - Logger.debug("replacing notification with identifier: \(notificationIdentifier)") - cancelNotification(identifier: notificationIdentifier) - } + + let notificationIdentifier = isReplacingNotification ? replacingIdentifier! : UUID().uuidString if shouldPresentNotification(category: category, userInfo: userInfo) { if let displayableTitle = title?.filterForDisplay { @@ -119,10 +120,26 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee { // Play sound and vibrate, but without a `body` no banner will show. Logger.debug("supressing notification body") } + + let trigger: UNNotificationTrigger? + if isBackgroudPoll { + trigger = UNTimeIntervalNotificationTrigger(timeInterval: kNotificationDelayForBackgroumdPoll, repeats: false) + let numberOfNotifications: Int + if let lastRequest = notifications[notificationIdentifier], let counter = lastRequest.content.userInfo[AppNotificationUserInfoKey.threadNotificationCounter] as? Int { + numberOfNotifications = counter + 1 + } else { + numberOfNotifications = 1 + } + content.userInfo[AppNotificationUserInfoKey.threadNotificationCounter] = numberOfNotifications + content.body = "\(numberOfNotifications) new messages." + } else { + trigger = nil + } - let request = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: nil) + let request = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: trigger) Logger.debug("presenting notification with identifier: \(notificationIdentifier)") + if isReplacingNotification { cancelNotification(identifier: notificationIdentifier) } notificationCenter.add(request) notifications[notificationIdentifier] = request } diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 5e9f987d9..dd44b29b0 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -342,7 +342,9 @@ extension MessageReceiver { // Notify the user if needed guard (isMainAppAndActive || isBackgroundPoll), let tsIncomingMessage = TSMessage.fetch(uniqueId: tsMessageID, transaction: transaction) as? TSIncomingMessage, let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return tsMessageID } - tsIncomingMessage.setNotificationIdentifier(UUID().uuidString, transaction: transaction) + // Use the same identifier for notifications when in backgroud polling to prevent spam + let notificationIdentifier = isBackgroundPoll ? thread.uniqueId : UUID().uuidString + tsIncomingMessage.setNotificationIdentifier(notificationIdentifier, transaction: transaction) DispatchQueue.main.async { Storage.read { transaction in SSKEnvironment.shared.notificationsManager!.notifyUser(for: tsIncomingMessage, in: thread, transaction: transaction)