diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 83be1b97b..44752a1f7 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -441,6 +441,7 @@ 4CA5F793211E1F06008C2708 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5F792211E1F06008C2708 /* Toast.swift */; }; 4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */; }; 4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB5F26820F7D060004D1B42 /* MessageActions.swift */; }; + 4CB93DC22180FF07004B9764 /* ProximityMonitoringManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */; }; 4CC0B59C20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */; }; 4CC1ECF9211A47CE00CC13BE /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4CC1ECF8211A47CD00CC13BE /* StoreKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */; }; @@ -1126,6 +1127,7 @@ 4C9CA25C217E676900607C63 /* ZXingObjC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ZXingObjC.framework; path = ThirdParty/Carthage/Build/iOS/ZXingObjC.framework; sourceTree = ""; }; 4CA5F792211E1F06008C2708 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; 4CB5F26820F7D060004D1B42 /* MessageActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActions.swift; sourceTree = ""; }; + 4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityMonitoringManager.swift; sourceTree = ""; }; 4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationConfigurationSyncOperation.swift; sourceTree = ""; }; 4CC1ECF8211A47CD00CC13BE /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; 4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateNag.swift; sourceTree = ""; }; @@ -1517,6 +1519,7 @@ 45F170D51E315310003FC1F2 /* Weak.swift */, 4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */, 4C948FF62146EB4800349F0D /* BlockListCache.swift */, + 4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */, ); path = utils; sourceTree = ""; @@ -3152,6 +3155,7 @@ files = ( 45F59A0A2029140500E8D2B0 /* OWSVideoPlayer.swift in Sources */, 45194F951FD7216600333B2C /* TSUnreadIndicatorInteraction.m in Sources */, + 4CB93DC22180FF07004B9764 /* ProximityMonitoringManager.swift in Sources */, 34AC09E1211B39B100997B47 /* SelectThreadViewController.m in Sources */, 34AC09EF211B39B100997B47 /* ViewControllerUtils.m in Sources */, 346941A2215D2EE400B5BFAD /* OWSConversationColor.m in Sources */, diff --git a/Signal/src/ViewControllers/CallViewController.swift b/Signal/src/ViewControllers/CallViewController.swift index 567417bcd..ff80cc755 100644 --- a/Signal/src/ViewControllers/CallViewController.swift +++ b/Signal/src/ViewControllers/CallViewController.swift @@ -13,10 +13,15 @@ import SignalMessaging class CallViewController: OWSViewController, CallObserver, CallServiceObserver, CallAudioServiceDelegate { // Dependencies + var callUIAdapter: CallUIAdapter { return AppEnvironment.shared.callService.callUIAdapter } + var proximityMonitoringManager: OWSProximityMonitoringManager { + return Environment.shared.proximityMonitoringManager + } + // Feature Flag @objc public static let kShowCallViewOnSeparateWindow = true @@ -165,6 +170,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) + self.proximityMonitoringManager.remove(lifetime: self) UIDevice.current.isProximityMonitoringEnabled = false callDurationTimer?.invalidate() @@ -173,7 +179,8 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - UIDevice.current.isProximityMonitoringEnabled = true + self.proximityMonitoringManager.add(lifetime: self) + updateCallUI(callState: call.state) self.becomeFirstResponder() diff --git a/SignalMessaging/environment/AppSetup.m b/SignalMessaging/environment/AppSetup.m index 5ad9cafa4..57e7e1940 100644 --- a/SignalMessaging/environment/AppSetup.m +++ b/SignalMessaging/environment/AppSetup.m @@ -86,12 +86,14 @@ NS_ASSUME_NONNULL_BEGIN OWSAudioSession *audioSession = [OWSAudioSession new]; OWSSounds *sounds = [[OWSSounds alloc] initWithPrimaryStorage:primaryStorage]; + id proximityMonitoringManager = [OWSProximityMonitoringManagerImpl new]; LockInteractionController *lockInteractionController = [[LockInteractionController alloc] initDefault]; OWSWindowManager *windowManager = [[OWSWindowManager alloc] initDefault]; [Environment setShared:[[Environment alloc] initWithAudioSession:audioSession + lockInteractionController:lockInteractionController preferences:preferences + proximityMonitoringManager:proximityMonitoringManager sounds:sounds - lockInteractionController:lockInteractionController windowManager:windowManager]]; [SSKEnvironment setShared:[[SSKEnvironment alloc] initWithContactsManager:contactsManager diff --git a/SignalMessaging/environment/Environment.h b/SignalMessaging/environment/Environment.h index ee3fadd1f..2ad9aa44a 100644 --- a/SignalMessaging/environment/Environment.h +++ b/SignalMessaging/environment/Environment.h @@ -11,6 +11,8 @@ @class OWSSounds; @class OWSWindowManager; +@protocol OWSProximityMonitoringManager; + /** * * Environment is a data and data accessor class. @@ -24,16 +26,18 @@ - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithAudioSession:(OWSAudioSession *)audioSession + lockInteractionController:(LockInteractionController *)lockInteractionController preferences:(OWSPreferences *)preferences + proximityMonitoringManager:(id)proximityMonitoringManager sounds:(OWSSounds *)sounds - lockInteractionController:(LockInteractionController *)lockInteractionController windowManager:(OWSWindowManager *)windowManager; @property (nonatomic, readonly) OWSAudioSession *audioSession; @property (nonatomic, readonly) OWSContactsManager *contactsManager; +@property (nonatomic, readonly) LockInteractionController *lockInteractionController; +@property (nonatomic, readonly) id proximityMonitoringManager; @property (nonatomic, readonly) OWSPreferences *preferences; @property (nonatomic, readonly) OWSSounds *sounds; -@property (nonatomic, readonly) LockInteractionController *lockInteractionController; @property (nonatomic, readonly) OWSWindowManager *windowManager; @property (class, nonatomic) Environment *shared; diff --git a/SignalMessaging/environment/Environment.m b/SignalMessaging/environment/Environment.m index 0a534c43f..d6bef0832 100644 --- a/SignalMessaging/environment/Environment.m +++ b/SignalMessaging/environment/Environment.m @@ -13,9 +13,10 @@ static Environment *sharedEnvironment = nil; @property (nonatomic) OWSAudioSession *audioSession; @property (nonatomic) OWSContactsManager *contactsManager; +@property (nonatomic) LockInteractionController *lockInteractionController; @property (nonatomic) OWSPreferences *preferences; +@property (nonatomic) id proximityMonitoringManager; @property (nonatomic) OWSSounds *sounds; -@property (nonatomic) LockInteractionController *lockInteractionController; @property (nonatomic) OWSWindowManager *windowManager; @end @@ -49,9 +50,10 @@ static Environment *sharedEnvironment = nil; } - (instancetype)initWithAudioSession:(OWSAudioSession *)audioSession + lockInteractionController:(LockInteractionController *)lockInteractionController preferences:(OWSPreferences *)preferences + proximityMonitoringManager:(id)proximityMonitoringManager sounds:(OWSSounds *)sounds - lockInteractionController:(LockInteractionController *)lockInteractionController windowManager:(OWSWindowManager *)windowManager { self = [super init]; @@ -60,15 +62,17 @@ static Environment *sharedEnvironment = nil; } OWSAssertDebug(audioSession); + OWSAssertDebug(lockInteractionController); OWSAssertDebug(preferences); + OWSAssertDebug(proximityMonitoringManager); OWSAssertDebug(sounds); - OWSAssertDebug(lockInteractionController); OWSAssertDebug(windowManager); _audioSession = audioSession; + _lockInteractionController = lockInteractionController; _preferences = preferences; + _proximityMonitoringManager = proximityMonitoringManager; _sounds = sounds; - _lockInteractionController = lockInteractionController; _windowManager = windowManager; OWSSingletonAssert(); diff --git a/SignalMessaging/environment/OWSAudioSession.swift b/SignalMessaging/environment/OWSAudioSession.swift index ef4868558..91996ebdd 100644 --- a/SignalMessaging/environment/OWSAudioSession.swift +++ b/SignalMessaging/environment/OWSAudioSession.swift @@ -44,6 +44,10 @@ public class OWSAudioSession: NSObject { // MARK: Dependencies + var proximityMonitoringManager: OWSProximityMonitoringManager { + return Environment.shared.proximityMonitoringManager + } + private let avAudioSession = AVAudioSession.sharedInstance() private let device = UIDevice.current @@ -65,30 +69,7 @@ public class OWSAudioSession: NSObject { self.currentActivities.append(Weak(value: audioActivity)) do { - if aggregateBehaviors.contains(.call) { - // Do nothing while on a call. - // WebRTC/CallAudioService manages call audio - // Eventually it would be nice to consolidate more of the audio - // session handling. - } else { - if aggregateBehaviors.contains(.playAndRecord) { - assert(avAudioSession.recordPermission() == .granted) - try avAudioSession.setCategory(AVAudioSessionCategoryRecord) - } else if aggregateBehaviors.contains(.audioMessagePlayback) { - try ensureCategoryForProximityState() - } else if aggregateBehaviors.contains(.playback) { - try avAudioSession.setCategory(AVAudioSessionCategoryPlayback) - } else { - owsFailDebug("no category option specified. Leaving category untouched.") - } - - if aggregateBehaviors.contains(.audioMessagePlayback) { - self.device.isProximityMonitoringEnabled = true - } else { - self.device.isProximityMonitoringEnabled = false - } - } - + try ensureAudioCategory() return true } catch { owsFailDebug("failed with error: \(error)") @@ -97,16 +78,36 @@ public class OWSAudioSession: NSObject { } @objc - func proximitySensorStateDidChange(notification: Notification) { + public func endAudioActivity(_ audioActivity: AudioActivity) { + Logger.debug("with audioActivity: \(audioActivity)") + + objc_sync_enter(self) + defer { objc_sync_exit(self) } + + currentActivities = currentActivities.filter { return $0.value != audioActivity } do { - try ensureCategoryForProximityState() + try ensureAudioCategory() } catch { - owsFailDebug("error in response to proximity change: \(error)") + owsFailDebug("error in ensureAudioCategory: \(error)") } } - func ensureCategoryForProximityState() throws { + func ensureAudioCategory() throws { if aggregateBehaviors.contains(.audioMessagePlayback) { + self.proximityMonitoringManager.add(lifetime: self) + } else { + self.proximityMonitoringManager.remove(lifetime: self) + } + + if aggregateBehaviors.contains(.call) { + // Do nothing while on a call. + // WebRTC/CallAudioService manages call audio + // Eventually it would be nice to consolidate more of the audio + // session handling. + } else if aggregateBehaviors.contains(.playAndRecord) { + assert(avAudioSession.recordPermission() == .granted) + try avAudioSession.setCategory(AVAudioSessionCategoryRecord) + } else if aggregateBehaviors.contains(.audioMessagePlayback) { if self.device.proximityState { Logger.debug("proximityState: true") @@ -116,23 +117,20 @@ public class OWSAudioSession: NSObject { Logger.debug("proximityState: false") try avAudioSession.setCategory(AVAudioSessionCategoryPlayback) } + } else if aggregateBehaviors.contains(.playback) { + try avAudioSession.setCategory(AVAudioSessionCategoryPlayback) + } else { + ensureAudioSessionActivationStateAfterDelay() } } @objc - public func endAudioActivity(_ audioActivity: AudioActivity) { - Logger.debug("with audioActivity: \(audioActivity)") - - objc_sync_enter(self) - defer { objc_sync_exit(self) } - - currentActivities = currentActivities.filter { return $0.value != audioActivity } + func proximitySensorStateDidChange(notification: Notification) { do { - try ensureCategoryForProximityState() + try ensureAudioCategory() } catch { - owsFailDebug("error in ensureProximityState: \(error)") + owsFailDebug("error in response to proximity change: \(error)") } - ensureAudioSessionActivationStateAfterDelay() } fileprivate func ensureAudioSessionActivationStateAfterDelay() { diff --git a/SignalMessaging/utils/ProximityMonitoringManager.swift b/SignalMessaging/utils/ProximityMonitoringManager.swift new file mode 100644 index 000000000..7d997a74c --- /dev/null +++ b/SignalMessaging/utils/ProximityMonitoringManager.swift @@ -0,0 +1,55 @@ +// +// Copyright (c) 2018 Open Whisper Systems. All rights reserved. +// + +@objc +public protocol OWSProximityMonitoringManager: class { + func add(lifetime: AnyObject) + func remove(lifetime: AnyObject) +} + +@objc +public class OWSProximityMonitoringManagerImpl: NSObject, OWSProximityMonitoringManager { + var lifetimes: [Weak] = [] + let serialQueue = DispatchQueue(label: "ProximityMonitoringManagerImpl") + + // MARK: + + var device: UIDevice { + return UIDevice.current + } + + // MARK: + + @objc + public func add(lifetime: AnyObject) { + serialQueue.sync { + if !lifetimes.contains { $0.value === lifetime } { + lifetimes.append(Weak(value: lifetime)) + } + reconcile() + } + } + + @objc + public func remove(lifetime: AnyObject) { + serialQueue.sync { + lifetimes = lifetimes.filter { $0.value !== lifetime } + reconcile() + } + } + + func reconcile() { + if _isDebugAssertConfiguration() { + assertOnQueue(serialQueue) + } + lifetimes = lifetimes.filter { $0.value != nil } + if lifetimes.isEmpty { + Logger.debug("disabling proximity monitoring") + device.isProximityMonitoringEnabled = false + } else { + Logger.debug("enabling proximity monitoring for lifetimes: \(lifetimes)") + device.isProximityMonitoringEnabled = true + } + } +}