Path: blob/main/Signal/util/DeviceSleepManagerImpl.swift
1 views
//
// 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<DeviceSleepBlockObject>]()
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
}
}