// // Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation import SignalServiceKit import UIKit /// This entity is responsible for keeping the screen on if certain /// behaviors (e.g., recording or playing voice messages) are in progress. /// /// Sleep blocking is achieved by using "block objects". For example, the /// audio player will add/remove a "block object" when it starts/stops. If /// there are any active "block objects", the device won't sleep. After they /// are all removed, the device will be able to sleep. class DeviceSleepManagerImpl: DeviceSleepManager { @MainActor private var blockObjects = [Weak]() init() { SwiftSingletons.register(self) } @MainActor func addBlock(blockObject: DeviceSleepBlockObject) { Logger.info("Adding sleep block: \(blockObject.blockReason)") blockObjects.append(Weak(value: blockObject)) ensureSleepBlocking() } @MainActor func removeBlock(blockObject: DeviceSleepBlockObject) { Logger.info("Removing sleep block: \(blockObject.blockReason)") blockObjects.removeAll(where: { $0.value === blockObject }) ensureSleepBlocking() } @MainActor private func ensureSleepBlocking() { // Cull expired blocks. if blockObjects.contains(where: { $0.value == nil }) { owsFailDebug("Callers must remove BlockObjects explicitly.") blockObjects.removeAll(where: { $0.value == nil }) } let blockObjects = self.blockObjects.compactMap(\.value) let shouldBeBlocking = !blockObjects.isEmpty if UIApplication.shared.isIdleTimerDisabled != shouldBeBlocking { if shouldBeBlocking { Logger.info("Blocking sleep.") } else { Logger.info("Unblocking sleep.") } } UIApplication.shared.isIdleTimerDisabled = shouldBeBlocking } }