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 6 years ago
parent 7f37400f1d
commit 5632bd2d83

@ -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 = "<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>"; };
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>"; };
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>"; };
@ -1517,6 +1519,7 @@
45F170D51E315310003FC1F2 /* Weak.swift */,
4C858A51212DC5E1001B45D3 /* UIImage+OWS.swift */,
4C948FF62146EB4800349F0D /* BlockListCache.swift */,
4CB93DC12180FF07004B9764 /* ProximityMonitoringManager.swift */,
);
path = utils;
sourceTree = "<group>";
@ -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 */,

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

@ -86,12 +86,14 @@ NS_ASSUME_NONNULL_BEGIN
OWSAudioSession *audioSession = [OWSAudioSession new];
OWSSounds *sounds = [[OWSSounds alloc] initWithPrimaryStorage:primaryStorage];
id<OWSProximityMonitoringManager> 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

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

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

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

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