// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation import SessionUtilitiesKit /// This entity has responsibility for blocking the device from sleeping if certain behaviors (e.g. recording or /// playing voice messages) are in progress. /// /// Sleep blocking is keyed using "block objects" whose lifetime corresponds to the duration of the block. For /// example, sleep blocking during audio playback can be keyed to the audio player. This provides a measure /// of robustness. /// /// On the one hand, we can use weak references to track block objects and stop blocking if the block object is /// deallocated even if removeBlock() is not called. On the other hand, we will also get correct behavior to addBlock() /// being called twice with the same block object. @objc public class DeviceSleepManager: NSObject { @objc public static let sharedInstance = DeviceSleepManager() private class SleepBlock: CustomDebugStringConvertible { weak var blockObject: NSObject? var debugDescription: String { return "SleepBlock(\(String(reflecting: blockObject)))" } init(blockObject: NSObject?) { self.blockObject = blockObject } } private var blocks: [SleepBlock] = [] private override init() { super.init() NotificationCenter.default.addObserver( self, selector: #selector(didEnterBackground), name: NSNotification.Name.OWSApplicationDidEnterBackground, object: nil ) } deinit { NotificationCenter.default.removeObserver(self) } @objc private func didEnterBackground() { ensureSleepBlocking() } @objc public func addBlock(blockObject: NSObject?) { blocks.append(SleepBlock(blockObject: blockObject)) ensureSleepBlocking() } @objc public func removeBlock(blockObject: NSObject?) { blocks = blocks.filter { $0.blockObject != nil && $0.blockObject != blockObject } ensureSleepBlocking() } private func ensureSleepBlocking() { // Cull expired blocks. blocks = blocks.filter { $0.blockObject != nil } let shouldBlock = blocks.count > 0 CurrentAppContext().ensureSleepBlocking(shouldBlock, blockingObjects: blocks) } }