Respect audio preferences/throttling
This commit is contained in:
parent
1bfe691895
commit
fe84275cce
|
@ -1508,10 +1508,11 @@ static NSTimeInterval launchStartedAt;
|
|||
{
|
||||
OWSLogInfo(@"");
|
||||
[AppReadiness runNowOrWhenAppDidBecomeReady:^() {
|
||||
// TODO move this into adaptee ?
|
||||
// we need to respect the in-app notification sound settings, either here
|
||||
// or maybe it's simpler to do that when building the notification. e.g. if the app
|
||||
// is in the forground when the notification was sent, we could just *not* add the sound.
|
||||
// We need to respect the in-app notification sound preference. This method, which is called
|
||||
// for modern UNUserNotification users, could be a place to do that, but since we'd still
|
||||
// need to handle this behavior for legacy UINotification users anyway, we "allow" all
|
||||
// notification options here, and rely on the shared logic in NotificationPresenter to
|
||||
// honor notification sound preferences for both modern and legacy users.
|
||||
UNNotificationPresentationOptions options = UNNotificationPresentationOptionAlert
|
||||
| UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound;
|
||||
completionHandler(options);
|
||||
|
|
|
@ -108,6 +108,9 @@ extension AppNotificationAction {
|
|||
// avoid notifying a user on their phone while a conversation is actively happening on desktop.
|
||||
let kNotificationDelayForRemoteRead: TimeInterval = 5
|
||||
|
||||
let kAudioNotificationsThrottleCount = 2
|
||||
let kAudioNotificationsThrottleInterval: TimeInterval = 5
|
||||
|
||||
protocol NotificationPresenterAdaptee: class {
|
||||
|
||||
func registerNotificationSettings() -> Promise<Void>
|
||||
|
@ -118,7 +121,6 @@ protocol NotificationPresenterAdaptee: class {
|
|||
func cancelNotifications(threadId: String)
|
||||
func clearAllNotifications()
|
||||
|
||||
var shouldPlaySoundForNotification: Bool { get }
|
||||
var hasReceivedSyncMessageRecently: Bool { get }
|
||||
}
|
||||
|
||||
|
@ -155,8 +157,12 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
return OWSIdentityManager.shared()
|
||||
}
|
||||
|
||||
var preferences: OWSPreferences {
|
||||
return Environment.shared.preferences
|
||||
}
|
||||
|
||||
var previewType: NotificationType {
|
||||
return Environment.shared.preferences.notificationPreviewType()
|
||||
return preferences.notificationPreviewType()
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
@ -222,13 +228,11 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
AppNotificationUserInfoKey.localCallId: call.localId.uuidString
|
||||
]
|
||||
|
||||
let sound = OWSSound.defaultiOSIncomingRingtone
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.adaptee.notify(category: .incomingCall,
|
||||
body: notificationBody,
|
||||
userInfo: userInfo,
|
||||
sound: sound,
|
||||
sound: .defaultiOSIncomingRingtone,
|
||||
replacingIdentifier: call.localId.uuidString)
|
||||
}
|
||||
}
|
||||
|
@ -250,19 +254,13 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
return
|
||||
}
|
||||
|
||||
let sound: OWSSound?
|
||||
if shouldPlaySoundForNotification {
|
||||
sound = OWSSounds.notificationSound(for: thread)
|
||||
} else {
|
||||
sound = nil
|
||||
}
|
||||
|
||||
let userInfo = [
|
||||
AppNotificationUserInfoKey.threadId: threadId,
|
||||
AppNotificationUserInfoKey.localCallId: call.localId.uuidString
|
||||
]
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let sound = self.requestSound(thread: thread)
|
||||
self.adaptee.notify(category: .missedCall,
|
||||
body: notificationBody,
|
||||
userInfo: userInfo,
|
||||
|
@ -287,18 +285,12 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
return
|
||||
}
|
||||
|
||||
let sound: OWSSound?
|
||||
if shouldPlaySoundForNotification {
|
||||
sound = OWSSounds.notificationSound(for: thread)
|
||||
} else {
|
||||
sound = nil
|
||||
}
|
||||
|
||||
let userInfo = [
|
||||
AppNotificationUserInfoKey.threadId: threadId
|
||||
]
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let sound = self.requestSound(thread: thread)
|
||||
self.adaptee.notify(category: .missedCallFromNoLongerVerifiedIdentity,
|
||||
body: notificationBody,
|
||||
userInfo: userInfo,
|
||||
|
@ -325,19 +317,13 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
return
|
||||
}
|
||||
|
||||
let sound: OWSSound?
|
||||
if shouldPlaySoundForNotification {
|
||||
sound = OWSSounds.notificationSound(for: thread)
|
||||
} else {
|
||||
sound = nil
|
||||
}
|
||||
|
||||
let userInfo = [
|
||||
AppNotificationUserInfoKey.threadId: threadId,
|
||||
AppNotificationUserInfoKey.callBackNumber: remotePhoneNumber
|
||||
]
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let sound = self.requestSound(thread: thread)
|
||||
self.adaptee.notify(category: .missedCall,
|
||||
body: notificationBody,
|
||||
userInfo: userInfo,
|
||||
|
@ -407,13 +393,6 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
let sound: OWSSound?
|
||||
if shouldPlaySoundForNotification {
|
||||
sound = OWSSounds.notificationSound(for: thread)
|
||||
} else {
|
||||
sound = nil
|
||||
}
|
||||
|
||||
guard let threadId = thread.uniqueId else {
|
||||
owsFailDebug("threadId was unexpectedly nil")
|
||||
return
|
||||
|
@ -434,6 +413,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
]
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let sound = self.requestSound(thread: thread)
|
||||
self.adaptee.notify(category: category, body: notificationBody, userInfo: userInfo, sound: sound)
|
||||
}
|
||||
}
|
||||
|
@ -442,13 +422,6 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
let notificationFormat = NSLocalizedString("NOTIFICATION_SEND_FAILED", comment: "subsequent notification body when replying from notification fails")
|
||||
let notificationBody = String(format: notificationFormat, thread.name())
|
||||
|
||||
let sound: OWSSound?
|
||||
if shouldPlaySoundForNotification {
|
||||
sound = OWSSounds.notificationSound(for: thread)
|
||||
} else {
|
||||
sound = nil
|
||||
}
|
||||
|
||||
guard let threadId = thread.uniqueId else {
|
||||
owsFailDebug("threadId was unexpectedly nil")
|
||||
return
|
||||
|
@ -459,6 +432,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
]
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let sound = self.requestSound(thread: thread)
|
||||
self.adaptee.notify(category: .errorMessage, body: notificationBody, userInfo: userInfo, sound: sound)
|
||||
}
|
||||
}
|
||||
|
@ -476,13 +450,6 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
notificationBody = messageText
|
||||
}
|
||||
|
||||
let sound: OWSSound?
|
||||
if shouldPlaySoundForNotification {
|
||||
sound = OWSSounds.notificationSound(for: thread)
|
||||
} else {
|
||||
sound = nil
|
||||
}
|
||||
|
||||
guard let threadId = thread.uniqueId else {
|
||||
owsFailDebug("threadId was unexpectedly nil")
|
||||
return
|
||||
|
@ -493,6 +460,7 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
]
|
||||
|
||||
transaction.addCompletionQueue(DispatchQueue.main) {
|
||||
let sound = self.requestSound(thread: thread)
|
||||
self.adaptee.notify(category: .errorMessage, body: notificationBody, userInfo: userInfo, sound: sound)
|
||||
}
|
||||
}
|
||||
|
@ -500,14 +468,8 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
public func notifyUser(forThreadlessErrorMessage errorMessage: TSErrorMessage, transaction: YapDatabaseReadWriteTransaction) {
|
||||
let notificationBody = errorMessage.previewText(with: transaction)
|
||||
|
||||
let sound: OWSSound?
|
||||
if shouldPlaySoundForNotification {
|
||||
sound = OWSSounds.globalNotificationSound()
|
||||
} else {
|
||||
sound = nil
|
||||
}
|
||||
|
||||
transaction.addCompletionQueue(DispatchQueue.main) {
|
||||
let sound = self.checkIfShouldPlaySound() ? OWSSounds.globalNotificationSound() : nil
|
||||
self.adaptee.notify(category: .threadlessErrorMessage, body: notificationBody, userInfo: [:], sound: sound)
|
||||
}
|
||||
}
|
||||
|
@ -520,9 +482,40 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
|||
adaptee.clearAllNotifications()
|
||||
}
|
||||
|
||||
// TODO rename to something like 'shouldThrottle' or 'requestAudioUsage'
|
||||
var shouldPlaySoundForNotification: Bool {
|
||||
return adaptee.shouldPlaySoundForNotification
|
||||
// MARK: -
|
||||
|
||||
var mostRecentNotifications = TruncatedList<UInt64>(maxLength: kAudioNotificationsThrottleCount)
|
||||
|
||||
private func requestSound(thread: TSThread) -> OWSSound? {
|
||||
guard checkIfShouldPlaySound() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return OWSSounds.notificationSound(for: thread)
|
||||
}
|
||||
|
||||
private func checkIfShouldPlaySound() -> Bool {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
guard UIApplication.shared.applicationState == .active else {
|
||||
return true
|
||||
}
|
||||
|
||||
guard preferences.soundInForeground() else {
|
||||
return false
|
||||
}
|
||||
|
||||
let now = NSDate.ows_millisecondTimeStamp()
|
||||
let recentThreshold = now - UInt64(kAudioNotificationsThrottleInterval * Double(kSecondInMs))
|
||||
|
||||
let recentNotifications = mostRecentNotifications.filter { $0 > recentThreshold }
|
||||
|
||||
guard recentNotifications.count < kAudioNotificationsThrottleCount else {
|
||||
return false
|
||||
}
|
||||
|
||||
mostRecentNotifications.append(now)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -654,12 +647,6 @@ extension ThreadUtil {
|
|||
}
|
||||
}
|
||||
|
||||
extension OWSSound {
|
||||
var filename: String? {
|
||||
return OWSSounds.filename(for: self)
|
||||
}
|
||||
}
|
||||
|
||||
enum NotificationError: Error {
|
||||
case assertionError(description: String)
|
||||
}
|
||||
|
@ -670,3 +657,38 @@ extension NotificationError {
|
|||
return NotificationError.assertionError(description: description)
|
||||
}
|
||||
}
|
||||
|
||||
struct TruncatedList<Element> {
|
||||
let maxLength: Int
|
||||
private var contents: [Element] = []
|
||||
|
||||
init(maxLength: Int) {
|
||||
self.maxLength = maxLength
|
||||
}
|
||||
|
||||
mutating func append(_ newElement: Element) {
|
||||
var newElements = self.contents
|
||||
newElements.append(newElement)
|
||||
self.contents = Array(newElements.suffix(maxLength))
|
||||
}
|
||||
}
|
||||
|
||||
extension TruncatedList: Collection {
|
||||
typealias Index = Int
|
||||
|
||||
var startIndex: Index {
|
||||
return contents.startIndex
|
||||
}
|
||||
|
||||
var endIndex: Index {
|
||||
return contents.endIndex
|
||||
}
|
||||
|
||||
subscript (position: Index) -> Element {
|
||||
return contents[position]
|
||||
}
|
||||
|
||||
func index(after i: Index) -> Index {
|
||||
return contents.index(after: i)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,7 +145,12 @@ extension LegacyNotificationPresenterAdaptee: NotificationPresenterAdaptee {
|
|||
func notify(category: AppNotificationCategory, body: String, userInfo: [AnyHashable: Any], sound: OWSSound?, replacingIdentifier: String?) {
|
||||
AssertIsOnMainThread()
|
||||
guard UIApplication.shared.applicationState != .active else {
|
||||
Logger.info("skipping notification; app is in foreground")
|
||||
if let sound = sound {
|
||||
let soundId = OWSSounds.systemSoundID(for: sound, quiet: true)
|
||||
|
||||
// Vibrate, respect silent switch, respect "Alert" volume, not media volume.
|
||||
AudioServicesPlayAlertSound(soundId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -210,12 +215,6 @@ extension LegacyNotificationPresenterAdaptee: NotificationPresenterAdaptee {
|
|||
cancelNotification(notification)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Accomodate 'playSoundsInForeground' preference
|
||||
// FIXME: debounce
|
||||
var shouldPlaySoundForNotification: Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
|
@ -288,3 +287,9 @@ public class LegacyNotificationActionHandler: NSObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension OWSSound {
|
||||
var filename: String? {
|
||||
return OWSSounds.filename(for: self, quiet: false)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,7 +112,8 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee {
|
|||
let content = UNMutableNotificationContent()
|
||||
content.categoryIdentifier = category.identifier
|
||||
content.userInfo = userInfo
|
||||
content.sound = sound?.notificationSound
|
||||
let isAppActive = UIApplication.shared.applicationState == .active
|
||||
content.sound = sound?.notificationSound(isQuiet: isAppActive)
|
||||
|
||||
var notificationIdentifier: String = UUID().uuidString
|
||||
if let replacingIdentifier = replacingIdentifier {
|
||||
|
@ -177,11 +178,6 @@ extension UserNotificationPresenterAdaptee: NotificationPresenterAdaptee {
|
|||
notificationCenter.removeAllDeliveredNotifications()
|
||||
}
|
||||
|
||||
// UNUserNotification framework does it's own audio throttling
|
||||
var shouldPlaySoundForNotification: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func shouldPresentNotification(category: AppNotificationCategory, userInfo: [AnyHashable: Any]) -> Bool {
|
||||
AssertIsOnMainThread()
|
||||
guard UIApplication.shared.applicationState == .active else {
|
||||
|
@ -278,8 +274,8 @@ public class UserNotificationActionHandler: NSObject {
|
|||
|
||||
extension OWSSound {
|
||||
@available(iOS 10.0, *)
|
||||
var notificationSound: UNNotificationSound {
|
||||
guard let filename = OWSSounds.filename(for: self) else {
|
||||
func notificationSound(isQuiet: Bool) -> UNNotificationSound {
|
||||
guard let filename = OWSSounds.filename(for: self, quiet: isQuiet) else {
|
||||
owsFailDebug("filename was unexpectedly nil")
|
||||
return UNNotificationSound.default()
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
||||
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
|
||||
//
|
||||
|
||||
#import "OWSAudioPlayer.h"
|
||||
|
@ -54,6 +54,7 @@ typedef NS_ENUM(NSUInteger, OWSSound) {
|
|||
+ (NSString *)displayNameForSound:(OWSSound)sound;
|
||||
|
||||
+ (nullable NSString *)filenameForSound:(OWSSound)sound;
|
||||
+ (nullable NSString *)filenameForSound:(OWSSound)sound quiet:(BOOL)quiet;
|
||||
|
||||
#pragma mark - Notifications
|
||||
|
||||
|
|
Loading…
Reference in New Issue