Use reference counting to disable proximity monitoring after audio message

Multiple overlapping activities require proximity monitoring (namely,
CallViewController and listening to audio messages).

These activities can overlap arbitrarily, so we use a reference counting
strategy to keep proximity monitoring on as long as one of these activities is
active.
pull/1/head
Michael Kirk 7 years ago
parent 7f37400f1d
commit 5632bd2d83

@ -441,6 +441,7 @@
4CA5F793211E1F06008C2708 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5F792211E1F06008C2708 /* Toast.swift */; }; 4CA5F793211E1F06008C2708 /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA5F792211E1F06008C2708 /* Toast.swift */; };
4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */; }; 4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF4C0920F55BBA005DA313 /* MenuActionsViewController.swift */; };
4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB5F26820F7D060004D1B42 /* MessageActions.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 */; }; 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, ); }; }; 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 */; }; 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 = "<group>"; }; 4C9CA25C217E676900607C63 /* ZXingObjC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ZXingObjC.framework; path = ThirdParty/Carthage/Build/iOS/ZXingObjC.framework; sourceTree = "<group>"; };
4CA5F792211E1F06008C2708 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = "<group>"; }; 4CA5F792211E1F06008C2708 /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = "<group>"; };
4CB5F26820F7D060004D1B42 /* MessageActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActions.swift; sourceTree = "<group>"; }; 4CB5F26820F7D060004D1B42 /* MessageActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActions.swift; sourceTree = "<group>"; };
4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityMonitoringManager.swift; sourceTree = "<group>"; };
4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationConfigurationSyncOperation.swift; sourceTree = "<group>"; }; 4CC0B59B20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationConfigurationSyncOperation.swift; sourceTree = "<group>"; };
4CC1ECF8211A47CD00CC13BE /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; 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 = "<group>"; }; 4CC1ECFA211A553000CC13BE /* AppUpdateNag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateNag.swift; sourceTree = "<group>"; };
@ -1517,6 +1519,7 @@
45F170D51E315310003FC1F2 /* Weak.swift */, 45F170D51E315310003FC1F2 /* Weak.swift */,
4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */, 4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */,
4C948FF62146EB4800349F0D /* BlockListCache.swift */, 4C948FF62146EB4800349F0D /* BlockListCache.swift */,
4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */,
); );
path = utils; path = utils;
sourceTree = "<group>"; sourceTree = "<group>";
@ -3152,6 +3155,7 @@
files = ( files = (
45F59A0A2029140500E8D2B0 /* OWSVideoPlayer.swift in Sources */, 45F59A0A2029140500E8D2B0 /* OWSVideoPlayer.swift in Sources */,
45194F951FD7216600333B2C /* TSUnreadIndicatorInteraction.m in Sources */, 45194F951FD7216600333B2C /* TSUnreadIndicatorInteraction.m in Sources */,
4CB93DC22180FF07004B9764 /* ProximityMonitoringManager.swift in Sources */,
34AC09E1211B39B100997B47 /* SelectThreadViewController.m in Sources */, 34AC09E1211B39B100997B47 /* SelectThreadViewController.m in Sources */,
34AC09EF211B39B100997B47 /* ViewControllerUtils.m in Sources */, 34AC09EF211B39B100997B47 /* ViewControllerUtils.m in Sources */,
346941A2215D2EE400B5BFAD /* OWSConversationColor.m in Sources */, 346941A2215D2EE400B5BFAD /* OWSConversationColor.m in Sources */,

@ -13,10 +13,15 @@ import SignalMessaging
class CallViewController: OWSViewController, CallObserver, CallServiceObserver, CallAudioServiceDelegate { class CallViewController: OWSViewController, CallObserver, CallServiceObserver, CallAudioServiceDelegate {
// Dependencies // Dependencies
var callUIAdapter: CallUIAdapter { var callUIAdapter: CallUIAdapter {
return AppEnvironment.shared.callService.callUIAdapter return AppEnvironment.shared.callService.callUIAdapter
} }
var proximityMonitoringManager: OWSProximityMonitoringManager {
return Environment.shared.proximityMonitoringManager
}
// Feature Flag // Feature Flag
@objc public static let kShowCallViewOnSeparateWindow = true @objc public static let kShowCallViewOnSeparateWindow = true
@ -165,6 +170,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
override func viewDidDisappear(_ animated: Bool) { override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated) super.viewDidDisappear(animated)
self.proximityMonitoringManager.remove(lifetime: self)
UIDevice.current.isProximityMonitoringEnabled = false UIDevice.current.isProximityMonitoringEnabled = false
callDurationTimer?.invalidate() callDurationTimer?.invalidate()
@ -173,7 +179,8 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver,
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
UIDevice.current.isProximityMonitoringEnabled = true self.proximityMonitoringManager.add(lifetime: self)
updateCallUI(callState: call.state) updateCallUI(callState: call.state)
self.becomeFirstResponder() self.becomeFirstResponder()

@ -86,12 +86,14 @@ NS_ASSUME_NONNULL_BEGIN
OWSAudioSession *audioSession = [OWSAudioSession new]; OWSAudioSession *audioSession = [OWSAudioSession new];
OWSSounds *sounds = [[OWSSounds alloc] initWithPrimaryStorage:primaryStorage]; OWSSounds *sounds = [[OWSSounds alloc] initWithPrimaryStorage:primaryStorage];
id<OWSProximityMonitoringManager> proximityMonitoringManager = [OWSProximityMonitoringManagerImpl new];
LockInteractionController *lockInteractionController = [[LockInteractionController alloc] initDefault]; LockInteractionController *lockInteractionController = [[LockInteractionController alloc] initDefault];
OWSWindowManager *windowManager = [[OWSWindowManager alloc] initDefault]; OWSWindowManager *windowManager = [[OWSWindowManager alloc] initDefault];
[Environment setShared:[[Environment alloc] initWithAudioSession:audioSession [Environment setShared:[[Environment alloc] initWithAudioSession:audioSession
lockInteractionController:lockInteractionController
preferences:preferences preferences:preferences
proximityMonitoringManager:proximityMonitoringManager
sounds:sounds sounds:sounds
lockInteractionController:lockInteractionController
windowManager:windowManager]]; windowManager:windowManager]];
[SSKEnvironment setShared:[[SSKEnvironment alloc] initWithContactsManager:contactsManager [SSKEnvironment setShared:[[SSKEnvironment alloc] initWithContactsManager:contactsManager

@ -11,6 +11,8 @@
@class OWSSounds; @class OWSSounds;
@class OWSWindowManager; @class OWSWindowManager;
@protocol OWSProximityMonitoringManager;
/** /**
* *
* Environment is a data and data accessor class. * Environment is a data and data accessor class.
@ -24,16 +26,18 @@
- (instancetype)init NS_UNAVAILABLE; - (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithAudioSession:(OWSAudioSession *)audioSession - (instancetype)initWithAudioSession:(OWSAudioSession *)audioSession
lockInteractionController:(LockInteractionController *)lockInteractionController
preferences:(OWSPreferences *)preferences preferences:(OWSPreferences *)preferences
proximityMonitoringManager:(id<OWSProximityMonitoringManager>)proximityMonitoringManager
sounds:(OWSSounds *)sounds sounds:(OWSSounds *)sounds
lockInteractionController:(LockInteractionController *)lockInteractionController
windowManager:(OWSWindowManager *)windowManager; windowManager:(OWSWindowManager *)windowManager;
@property (nonatomic, readonly) OWSAudioSession *audioSession; @property (nonatomic, readonly) OWSAudioSession *audioSession;
@property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic, readonly) LockInteractionController *lockInteractionController;
@property (nonatomic, readonly) id<OWSProximityMonitoringManager> proximityMonitoringManager;
@property (nonatomic, readonly) OWSPreferences *preferences; @property (nonatomic, readonly) OWSPreferences *preferences;
@property (nonatomic, readonly) OWSSounds *sounds; @property (nonatomic, readonly) OWSSounds *sounds;
@property (nonatomic, readonly) LockInteractionController *lockInteractionController;
@property (nonatomic, readonly) OWSWindowManager *windowManager; @property (nonatomic, readonly) OWSWindowManager *windowManager;
@property (class, nonatomic) Environment *shared; @property (class, nonatomic) Environment *shared;

@ -13,9 +13,10 @@ static Environment *sharedEnvironment = nil;
@property (nonatomic) OWSAudioSession *audioSession; @property (nonatomic) OWSAudioSession *audioSession;
@property (nonatomic) OWSContactsManager *contactsManager; @property (nonatomic) OWSContactsManager *contactsManager;
@property (nonatomic) LockInteractionController *lockInteractionController;
@property (nonatomic) OWSPreferences *preferences; @property (nonatomic) OWSPreferences *preferences;
@property (nonatomic) id<OWSProximityMonitoringManager> proximityMonitoringManager;
@property (nonatomic) OWSSounds *sounds; @property (nonatomic) OWSSounds *sounds;
@property (nonatomic) LockInteractionController *lockInteractionController;
@property (nonatomic) OWSWindowManager *windowManager; @property (nonatomic) OWSWindowManager *windowManager;
@end @end
@ -49,9 +50,10 @@ static Environment *sharedEnvironment = nil;
} }
- (instancetype)initWithAudioSession:(OWSAudioSession *)audioSession - (instancetype)initWithAudioSession:(OWSAudioSession *)audioSession
lockInteractionController:(LockInteractionController *)lockInteractionController
preferences:(OWSPreferences *)preferences preferences:(OWSPreferences *)preferences
proximityMonitoringManager:(id<OWSProximityMonitoringManager>)proximityMonitoringManager
sounds:(OWSSounds *)sounds sounds:(OWSSounds *)sounds
lockInteractionController:(LockInteractionController *)lockInteractionController
windowManager:(OWSWindowManager *)windowManager windowManager:(OWSWindowManager *)windowManager
{ {
self = [super init]; self = [super init];
@ -60,15 +62,17 @@ static Environment *sharedEnvironment = nil;
} }
OWSAssertDebug(audioSession); OWSAssertDebug(audioSession);
OWSAssertDebug(lockInteractionController);
OWSAssertDebug(preferences); OWSAssertDebug(preferences);
OWSAssertDebug(proximityMonitoringManager);
OWSAssertDebug(sounds); OWSAssertDebug(sounds);
OWSAssertDebug(lockInteractionController);
OWSAssertDebug(windowManager); OWSAssertDebug(windowManager);
_audioSession = audioSession; _audioSession = audioSession;
_lockInteractionController = lockInteractionController;
_preferences = preferences; _preferences = preferences;
_proximityMonitoringManager = proximityMonitoringManager;
_sounds = sounds; _sounds = sounds;
_lockInteractionController = lockInteractionController;
_windowManager = windowManager; _windowManager = windowManager;
OWSSingletonAssert(); OWSSingletonAssert();

@ -44,6 +44,10 @@ public class OWSAudioSession: NSObject {
// MARK: Dependencies // MARK: Dependencies
var proximityMonitoringManager: OWSProximityMonitoringManager {
return Environment.shared.proximityMonitoringManager
}
private let avAudioSession = AVAudioSession.sharedInstance() private let avAudioSession = AVAudioSession.sharedInstance()
private let device = UIDevice.current private let device = UIDevice.current
@ -65,30 +69,7 @@ public class OWSAudioSession: NSObject {
self.currentActivities.append(Weak(value: audioActivity)) self.currentActivities.append(Weak(value: audioActivity))
do { do {
if aggregateBehaviors.contains(.call) { try ensureAudioCategory()
// 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
}
}
return true return true
} catch { } catch {
owsFailDebug("failed with error: \(error)") owsFailDebug("failed with error: \(error)")
@ -97,16 +78,36 @@ public class OWSAudioSession: NSObject {
} }
@objc @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 { do {
try ensureCategoryForProximityState() try ensureAudioCategory()
} catch { } catch {
owsFailDebug("error in response to proximity change: \(error)") owsFailDebug("error in ensureAudioCategory: \(error)")
} }
} }
func ensureCategoryForProximityState() throws { func ensureAudioCategory() throws {
if aggregateBehaviors.contains(.audioMessagePlayback) { 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 { if self.device.proximityState {
Logger.debug("proximityState: true") Logger.debug("proximityState: true")
@ -116,23 +117,20 @@ public class OWSAudioSession: NSObject {
Logger.debug("proximityState: false") Logger.debug("proximityState: false")
try avAudioSession.setCategory(AVAudioSessionCategoryPlayback) try avAudioSession.setCategory(AVAudioSessionCategoryPlayback)
} }
} else if aggregateBehaviors.contains(.playback) {
try avAudioSession.setCategory(AVAudioSessionCategoryPlayback)
} else {
ensureAudioSessionActivationStateAfterDelay()
} }
} }
@objc @objc
public func endAudioActivity(_ audioActivity: AudioActivity) { func proximitySensorStateDidChange(notification: Notification) {
Logger.debug("with audioActivity: \(audioActivity)")
objc_sync_enter(self)
defer { objc_sync_exit(self) }
currentActivities = currentActivities.filter { return $0.value != audioActivity }
do { do {
try ensureCategoryForProximityState() try ensureAudioCategory()
} catch { } catch {
owsFailDebug("error in ensureProximityState: \(error)") owsFailDebug("error in response to proximity change: \(error)")
} }
ensureAudioSessionActivationStateAfterDelay()
} }
fileprivate func ensureAudioSessionActivationStateAfterDelay() { fileprivate func ensureAudioSessionActivationStateAfterDelay() {

@ -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<AnyObject>] = []
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
}
}
}
Loading…
Cancel
Save