Respect audio preferences/throttling

This commit is contained in:
Michael Kirk 2019-01-30 15:27:53 -07:00
parent 1bfe691895
commit fe84275cce
5 changed files with 108 additions and 83 deletions

View File

@ -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);

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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