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/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/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.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..9e29a8653 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 @@ -13,9 +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 *cachedSystemSounds; @end @@ -52,6 +95,9 @@ 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. + _cachedSystemSounds = [[AnyLRUCache alloc] initWithMaxSize:3]; + OWSSingletonAssert(); return self; @@ -207,6 +253,28 @@ 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]; + OWSSystemSound *_Nullable cachedSound = (OWSSystemSound *)[self.cachedSystemSounds getWithKey:cacheKey]; + + 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]; + + return newSound.soundID; +} + #pragma mark - Notifications + (OWSSound)defaultNotificationSound 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) + } + } +} 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();