// // Copyright 2018 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation public protocol OWSProximityMonitoringManager: AnyObject { func add(lifetime: AnyObject) func remove(lifetime: AnyObject) } public class OWSProximityMonitoringManagerImpl: OWSProximityMonitoringManager { public init() {} private struct State { var didAddObserver = false var lifetimes = [Weak]() } private var state = AtomicValue(State(), lock: .init()) public func add(lifetime: AnyObject) { guard !CurrentAppContext().isNSE else { return } updateState { state in if state.lifetimes.contains(where: { $0.value === lifetime }) { return } state.lifetimes.append(Weak(value: lifetime)) } } public func remove(lifetime: AnyObject) { guard !CurrentAppContext().isNSE else { return } updateState { state in state.lifetimes = state.lifetimes.filter { $0.value !== lifetime } } } private func updateState(block: (inout State) -> Void) { state.update { mutableState in let oldEnabled = !mutableState.lifetimes.isEmpty block(&mutableState) mutableState.lifetimes = mutableState.lifetimes.filter { $0.value !== nil } let newEnabled = !mutableState.lifetimes.isEmpty if oldEnabled == newEnabled { return } didChangeEnabled(newValue: newEnabled, state: &mutableState) } } @objc private func proximitySensorStateDidChange(notification: Notification) { Logger.debug("") // This is crazy, but if we disable `device.isProximityMonitoringEnabled` // while `device.proximityState` is true (while the device is held to the // ear) then `device.proximityState` remains true, even after we bring the // phone away from the ear and re-enable monitoring. // // To resolve this, we wait to disable proximity monitoring until // `proximityState` is false. if UIDevice.current.proximityState { self.add(lifetime: self) } else { self.remove(lifetime: self) } } private func didChangeEnabled(newValue: Bool, state: inout State) { Logger.debug("Proximity monitoring changed to \(newValue)") if newValue, !state.didAddObserver { state.didAddObserver = true DispatchQueue.main.async { NotificationCenter.default.addObserver( self, selector: #selector(self.proximitySensorStateDidChange(notification:)), name: UIDevice.proximityStateDidChangeNotification, object: nil ) } } DispatchQueue.main.async { UIDevice.current.isProximityMonitoringEnabled = newValue } } }