From 3cb53f5f4420785b12f43c382dc769ee514ed47e Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Thu, 29 Mar 2018 12:57:55 -0400 Subject: [PATCH 1/3] Respect system alert volume for notifications while in app // FREEBIE --- Signal/src/environment/NotificationsManager.m | 10 ++++--- SignalMessaging/environment/OWSSounds.h | 3 ++ SignalMessaging/environment/OWSSounds.m | 29 +++++++++++++++++++ SignalMessaging/utils/OWSAudioPlayer.h | 3 -- SignalMessaging/utils/OWSAudioPlayer.m | 9 ------ 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/Signal/src/environment/NotificationsManager.m b/Signal/src/environment/NotificationsManager.m index 7c2503dcf..a09ab3b65 100644 --- a/Signal/src/environment/NotificationsManager.m +++ b/Signal/src/environment/NotificationsManager.m @@ -239,8 +239,9 @@ } else { if (shouldPlaySound && [Environment.preferences soundInForeground]) { OWSSound sound = [OWSSounds notificationSoundForThread:thread]; - self.audioPlayer = [OWSSounds audioPlayerForSound:sound]; - [self.audioPlayer playAsForegroundAlert]; + SystemSoundID soundId = [OWSSounds systemSoundIDForSound:sound quiet:YES]; + // Vibrate, respect silent switch, respect "Alert" volume, not media volume. + AudioServicesPlayAlertSound(soundId); } } }); @@ -345,8 +346,9 @@ } else { if (shouldPlaySound && [Environment.preferences soundInForeground]) { OWSSound sound = [OWSSounds notificationSoundForThread:thread]; - self.audioPlayer = [OWSSounds audioPlayerForSound:sound]; - [self.audioPlayer playAsForegroundAlert]; + SystemSoundID soundId = [OWSSounds systemSoundIDForSound:sound quiet:YES]; + // Vibrate, respect silent switch, respect "Alert" volume, not media volume. + AudioServicesPlayAlertSound(soundId); } } }); diff --git a/SignalMessaging/environment/OWSSounds.h b/SignalMessaging/environment/OWSSounds.h index 506560d13..f2f311dd7 100644 --- a/SignalMessaging/environment/OWSSounds.h +++ b/SignalMessaging/environment/OWSSounds.h @@ -2,6 +2,8 @@ // Copyright (c) 2018 Open Whisper Systems. All rights reserved. // +#import + NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSUInteger, OWSSound) { @@ -57,6 +59,7 @@ typedef NS_ENUM(NSUInteger, OWSSound) { + (void)setGlobalNotificationSound:(OWSSound)sound transaction:(YapDatabaseReadWriteTransaction *)transaction; + (OWSSound)notificationSoundForThread:(TSThread *)thread; ++ (SystemSoundID)systemSoundIDForSound:(OWSSound)sound quiet:(BOOL)quiet; + (void)setNotificationSound:(OWSSound)sound forThread:(TSThread *)thread; #pragma mark - AudioPlayer diff --git a/SignalMessaging/environment/OWSSounds.m b/SignalMessaging/environment/OWSSounds.m index 53c36daad..ee515f258 100644 --- a/SignalMessaging/environment/OWSSounds.m +++ b/SignalMessaging/environment/OWSSounds.m @@ -16,6 +16,7 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob @interface OWSSounds () @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; +@property (nonatomic, readonly) NSMutableDictionary *cachedSoundIDs; @end @@ -51,6 +52,7 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob OWSAssert(primaryStorage); _dbConnection = primaryStorage.newDatabaseConnection; + _cachedSoundIDs = [NSMutableDictionary new]; OWSSingletonAssert(); @@ -207,6 +209,33 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob return url; } ++ (SystemSoundID)systemSoundIDForSound:(OWSSound)sound quiet:(BOOL)quiet +{ + return [self.sharedManager systemSoundIDForSound:(OWSSound)sound quiet:quiet]; +} + +- (SystemSoundID)systemSoundIDForSound:(OWSSound)sound quiet:(BOOL)quiet +{ + NSString *cacheKey = [NSString stringWithFormat:@"%lu:%d", (unsigned long)sound, quiet]; + NSNumber *cachedSoundId = self.cachedSoundIDs[cacheKey]; + + if (cachedSoundId) { + return (SystemSoundID)cachedSoundId.intValue; + } + + NSURL *soundURL = [self.class soundURLForSound:sound quiet:quiet]; + + DDLogVerbose(@"%@ creating system sound for %@", self.logTag, soundURL.lastPathComponent); + SystemSoundID newSoundID; + OSStatus status = AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(soundURL), &newSoundID); + OWSAssert(status == 0); + OWSAssert(newSoundID); + + self.cachedSoundIDs[cacheKey] = @(newSoundID); + + return newSoundID; +} + #pragma mark - Notifications + (OWSSound)defaultNotificationSound diff --git a/SignalMessaging/utils/OWSAudioPlayer.h b/SignalMessaging/utils/OWSAudioPlayer.h index 10a5acfaa..98a9a2f13 100644 --- a/SignalMessaging/utils/OWSAudioPlayer.h +++ b/SignalMessaging/utils/OWSAudioPlayer.h @@ -38,9 +38,6 @@ typedef NS_ENUM(NSInteger, AudioPlaybackState) { // respects silent switch - (void)playWithCurrentAudioCategory; -// respects silent switch, mixes with others -- (void)playAsForegroundAlert; - // will ensure sound is audible, even if silent switch is enabled - (void)playWithPlaybackAudioCategory; diff --git a/SignalMessaging/utils/OWSAudioPlayer.m b/SignalMessaging/utils/OWSAudioPlayer.m index bb146d117..7863704ef 100644 --- a/SignalMessaging/utils/OWSAudioPlayer.m +++ b/SignalMessaging/utils/OWSAudioPlayer.m @@ -104,15 +104,6 @@ NS_ASSUME_NONNULL_BEGIN [self play]; } -- (void)playAsForegroundAlert -{ - OWSAssertIsOnMainThread(); - [OWSAudioSession.shared startAmbientAudioActivity:self.audioActivity]; - - AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); - [self play]; -} - - (void)play { OWSAssertIsOnMainThread(); From 2580c690cb8a0643c5f9413c291005fd274c1ea4 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 30 Mar 2018 17:31:06 -0400 Subject: [PATCH 2/3] CR: Use LRU Cache for storing system sounds // FREEBIE --- Signal.xcodeproj/project.pbxproj | 4 ++ Signal/src/network/GiphyDownloader.swift | 43 ---------------- SignalMessaging/environment/OWSSounds.m | 12 +++-- SignalMessaging/utils/LRUCache.swift | 64 ++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 47 deletions(-) create mode 100644 SignalMessaging/utils/LRUCache.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index cf24874c3..034cc18aa 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -275,6 +275,7 @@ 4521C3C01F59F3BA00B4C582 /* TextFieldHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */; }; 4523149E1F7E916B003A428C /* SlideOffAnimatedTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523149D1F7E916B003A428C /* SlideOffAnimatedTransition.swift */; }; 452314A01F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */; }; + 4523D016206EDC2B00A2AB51 /* LRUCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4523D015206EDC2B00A2AB51 /* LRUCache.swift */; }; 452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */; }; 452C7CA72037628B003D51A5 /* Weak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170D51E315310003FC1F2 /* Weak.swift */; }; 452D1EE81DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 452D1EE71DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift */; }; @@ -874,6 +875,7 @@ 4521C3BF1F59F3BA00B4C582 /* TextFieldHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldHelper.swift; sourceTree = ""; }; 4523149D1F7E916B003A428C /* SlideOffAnimatedTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SlideOffAnimatedTransition.swift; path = UserInterface/SlideOffAnimatedTransition.swift; sourceTree = ""; }; 4523149F1F7E9E18003A428C /* DirectionalPanGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectionalPanGestureRecognizer.swift; sourceTree = ""; }; + 4523D015206EDC2B00A2AB51 /* LRUCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LRUCache.swift; sourceTree = ""; }; 452C468E1E427E200087B011 /* OutboundCallInitiator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutboundCallInitiator.swift; sourceTree = ""; }; 452D1EE71DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MesssagesBubblesSizeCalculatorTest.swift; path = Models/MesssagesBubblesSizeCalculatorTest.swift; sourceTree = ""; }; 452EA09D1EA7ABE00078744B /* AttachmentPointerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttachmentPointerView.swift; sourceTree = ""; }; @@ -1356,6 +1358,7 @@ 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */, 344F248C2007CCD600CFB4F4 /* DisplayableText.swift */, 346129AC1FD1F34E00532771 /* ImageCache.swift */, + 4523D015206EDC2B00A2AB51 /* LRUCache.swift */, 34C3C7902040B0DC0000134C /* OWSAudioPlayer.h */, 34C3C7912040B0DC0000134C /* OWSAudioPlayer.m */, 45666EC41D99483D008FE134 /* OWSAvatarBuilder.h */, @@ -3037,6 +3040,7 @@ 45BE4EA22012AD2000935E59 /* DisappearingTimerConfigurationView.swift in Sources */, 346129F71FD5F31400532771 /* OWS105AttachmentFilePaths.m in Sources */, 45194F931FD7215C00333B2C /* OWSContactOffersInteraction.m in Sources */, + 4523D016206EDC2B00A2AB51 /* LRUCache.swift in Sources */, 344F249A200FD03300CFB4F4 /* MessageApprovalViewController.swift in Sources */, 450998681FD8C0FF00D89EB3 /* AttachmentSharing.m in Sources */, 347850711FDAEB17007B8332 /* OWSUserProfile.m in Sources */, diff --git a/Signal/src/network/GiphyDownloader.swift b/Signal/src/network/GiphyDownloader.swift index 1cbd9bfe6..40071753e 100644 --- a/Signal/src/network/GiphyDownloader.swift +++ b/Signal/src/network/GiphyDownloader.swift @@ -368,49 +368,6 @@ enum GiphyAssetRequestState: UInt { } } -// A simple LRU cache bounded by the number of entries. -// -// TODO: We might want to observe memory pressure notifications. -class LRUCache { - - private var cacheMap = [KeyType: ValueType]() - private var cacheOrder = [KeyType]() - private let maxSize: Int - - init(maxSize: Int) { - self.maxSize = maxSize - } - - public func get(key: KeyType) -> ValueType? { - guard let value = cacheMap[key] else { - return nil - } - - // Update cache order. - cacheOrder = cacheOrder.filter { $0 != key } - cacheOrder.append(key) - - return value - } - - public func set(key: KeyType, value: ValueType) { - cacheMap[key] = value - - // Update cache order. - cacheOrder = cacheOrder.filter { $0 != key } - cacheOrder.append(key) - - while cacheOrder.count > maxSize { - guard let staleKey = cacheOrder.first else { - owsFail("Cache ordering unexpectedly empty") - return - } - cacheOrder.removeFirst() - cacheMap.removeValue(forKey: staleKey) - } - } -} - private var URLSessionTaskGiphyAssetRequest: UInt8 = 0 private var URLSessionTaskGiphyAssetSegment: UInt8 = 0 diff --git a/SignalMessaging/environment/OWSSounds.m b/SignalMessaging/environment/OWSSounds.m index ee515f258..09600990e 100644 --- a/SignalMessaging/environment/OWSSounds.m +++ b/SignalMessaging/environment/OWSSounds.m @@ -4,6 +4,7 @@ #import "OWSSounds.h" #import "OWSAudioPlayer.h" +#import #import #import #import @@ -16,7 +17,7 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob @interface OWSSounds () @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (nonatomic, readonly) NSMutableDictionary *cachedSoundIDs; +@property (nonatomic, readonly) AnyLRUCache *cachedSoundIDs; @end @@ -52,7 +53,9 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob OWSAssert(primaryStorage); _dbConnection = primaryStorage.newDatabaseConnection; - _cachedSoundIDs = [NSMutableDictionary new]; + + // Don't store too many sounds in memory. Most users will only use 1 or 2 sounds anyway. + _cachedSoundIDs = [[AnyLRUCache alloc] initWithMaxSize:3]; OWSSingletonAssert(); @@ -217,9 +220,10 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob - (SystemSoundID)systemSoundIDForSound:(OWSSound)sound quiet:(BOOL)quiet { NSString *cacheKey = [NSString stringWithFormat:@"%lu:%d", (unsigned long)sound, quiet]; - NSNumber *cachedSoundId = self.cachedSoundIDs[cacheKey]; + NSNumber *_Nullable cachedSoundId = (NSNumber * _Nullable)[self.cachedSoundIDs getWithKey:cacheKey]; if (cachedSoundId) { + OWSAssert([cachedSoundId isKindOfClass:[NSNumber class]]); return (SystemSoundID)cachedSoundId.intValue; } @@ -231,7 +235,7 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob OWSAssert(status == 0); OWSAssert(newSoundID); - self.cachedSoundIDs[cacheKey] = @(newSoundID); + [self.cachedSoundIDs setWithKey:cacheKey value:@(newSoundID)]; return newSoundID; } diff --git a/SignalMessaging/utils/LRUCache.swift b/SignalMessaging/utils/LRUCache.swift new file mode 100644 index 000000000..88c86a560 --- /dev/null +++ b/SignalMessaging/utils/LRUCache.swift @@ -0,0 +1,64 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +@objc +public class AnyLRUCache: NSObject { + + let backingCache: LRUCache + + public init(maxSize: Int) { + backingCache = LRUCache(maxSize: maxSize) + } + + public func get(key: NSObject) -> NSObject? { + return self.backingCache.get(key: key) + } + + public func set(key: NSObject, value: NSObject) { + self.backingCache.set(key: key, value: value) + } +} + +// A simple LRU cache bounded by the number of entries. +// +// TODO: We might want to observe memory pressure notifications. +public class LRUCache { + + private var cacheMap: [KeyType: ValueType] = [:] + private var cacheOrder: [KeyType] = [] + private let maxSize: Int + + public init(maxSize: Int) { + self.maxSize = maxSize + } + + public func get(key: KeyType) -> ValueType? { + guard let value = cacheMap[key] else { + return nil + } + + // Update cache order. + cacheOrder = cacheOrder.filter { $0 != key } + cacheOrder.append(key) + + return value + } + + public func set(key: KeyType, value: ValueType) { + cacheMap[key] = value + + // Update cache order. + cacheOrder = cacheOrder.filter { $0 != key } + cacheOrder.append(key) + + while cacheOrder.count > maxSize { + guard let staleKey = cacheOrder.first else { + owsFail("Cache ordering unexpectedly empty") + return + } + cacheOrder.removeFirst() + cacheMap.removeValue(forKey: staleKey) + } + } +} From 5f8b3ff28527cd0edaa298432efad6fba354b678 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 3 Apr 2018 11:54:55 -0400 Subject: [PATCH 3/3] Object lifecycle disposes SystemSound // FREEBIE --- SignalMessaging/environment/OWSSounds.m | 65 +++++++++++++++++++------ 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/SignalMessaging/environment/OWSSounds.m b/SignalMessaging/environment/OWSSounds.m index 09600990e..9e29a8653 100644 --- a/SignalMessaging/environment/OWSSounds.m +++ b/SignalMessaging/environment/OWSSounds.m @@ -14,10 +14,51 @@ NSString *const kOWSSoundsStorageNotificationCollection = @"kOWSSoundsStorageNotificationCollection"; NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlobalNotificationKey"; +@interface OWSSystemSound : NSObject + +@property (nonatomic, readonly) SystemSoundID soundID; +@property (nonatomic, readonly) NSURL *soundURL; + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithURL:(NSURL *)url NS_DESIGNATED_INITIALIZER; + +@end + +@implementation OWSSystemSound + +- (instancetype)initWithURL:(NSURL *)url +{ + self = [super init]; + + if (!self) { + return self; + } + + DDLogDebug(@"%@ creating system sound for %@", self.logTag, url.lastPathComponent); + _soundURL = url; + + SystemSoundID newSoundID; + OSStatus status = AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(url), &newSoundID); + OWSAssert(status == kAudioServicesNoError); + OWSAssert(newSoundID); + _soundID = newSoundID; + + return self; +} + +- (void)dealloc +{ + DDLogDebug(@"%@ in dealloc disposing sound: %@", self.logTag, _soundURL.lastPathComponent); + OSStatus status = AudioServicesDisposeSystemSoundID(_soundID); + OWSAssert(status == kAudioServicesNoError); +} + +@end + @interface OWSSounds () @property (nonatomic, readonly) YapDatabaseConnection *dbConnection; -@property (nonatomic, readonly) AnyLRUCache *cachedSoundIDs; +@property (nonatomic, readonly) AnyLRUCache *cachedSystemSounds; @end @@ -55,7 +96,7 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob _dbConnection = primaryStorage.newDatabaseConnection; // Don't store too many sounds in memory. Most users will only use 1 or 2 sounds anyway. - _cachedSoundIDs = [[AnyLRUCache alloc] initWithMaxSize:3]; + _cachedSystemSounds = [[AnyLRUCache alloc] initWithMaxSize:3]; OWSSingletonAssert(); @@ -220,24 +261,18 @@ NSString *const kOWSSoundsStorageGlobalNotificationKey = @"kOWSSoundsStorageGlob - (SystemSoundID)systemSoundIDForSound:(OWSSound)sound quiet:(BOOL)quiet { NSString *cacheKey = [NSString stringWithFormat:@"%lu:%d", (unsigned long)sound, quiet]; - NSNumber *_Nullable cachedSoundId = (NSNumber * _Nullable)[self.cachedSoundIDs getWithKey:cacheKey]; + OWSSystemSound *_Nullable cachedSound = (OWSSystemSound *)[self.cachedSystemSounds getWithKey:cacheKey]; - if (cachedSoundId) { - OWSAssert([cachedSoundId isKindOfClass:[NSNumber class]]); - return (SystemSoundID)cachedSoundId.intValue; + if (cachedSound) { + OWSAssert([cachedSound isKindOfClass:[OWSSystemSound class]]); + return cachedSound.soundID; } NSURL *soundURL = [self.class soundURLForSound:sound quiet:quiet]; + OWSSystemSound *newSound = [[OWSSystemSound alloc] initWithURL:soundURL]; + [self.cachedSystemSounds setWithKey:cacheKey value:newSound]; - DDLogVerbose(@"%@ creating system sound for %@", self.logTag, soundURL.lastPathComponent); - SystemSoundID newSoundID; - OSStatus status = AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(soundURL), &newSoundID); - OWSAssert(status == 0); - OWSAssert(newSoundID); - - [self.cachedSoundIDs setWithKey:cacheKey value:@(newSoundID)]; - - return newSoundID; + return newSound.soundID; } #pragma mark - Notifications