// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation import SignalServiceKit class MainAppContext: NSObject, AppContext { let appLaunchTime: Date private(set) var appForegroundTime: Date override init() { _reportedApplicationState = AtomicValue(.inactive, lock: .init()) let launchDate = Date() appLaunchTime = launchDate appForegroundTime = launchDate _mainApplicationStateOnLaunch = UIApplication.shared.applicationState super.init() let notificationCenter = NotificationCenter.default notificationCenter.addObserver( self, selector: #selector(applicationWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil ) notificationCenter.addObserver( self, selector: #selector(applicationDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil ) notificationCenter.addObserver( self, selector: #selector(applicationWillResignActive), name: UIApplication.willResignActiveNotification, object: nil ) notificationCenter.addObserver( self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil ) } private let _mainApplicationStateOnLaunch: UIApplication.State func mainApplicationStateOnLaunch() -> UIApplication.State { _mainApplicationStateOnLaunch } private let _reportedApplicationState: AtomicValue var reportedApplicationState: UIApplication.State { get { _reportedApplicationState.get() } set { AssertIsOnMainThread() _reportedApplicationState.set(newValue) } } @objc private func applicationWillEnterForeground(_ notification: Notification) { AssertIsOnMainThread() self.reportedApplicationState = .inactive self.appForegroundTime = Date() BenchManager.bench(title: "Slow WillEnterForeground", logIfLongerThan: 0.2, logInProduction: true) { NotificationCenter.default.post(name: .OWSApplicationWillEnterForeground, object: nil) } } @objc private func applicationDidEnterBackground(_ notification: Notification) { AssertIsOnMainThread() self.reportedApplicationState = .background BenchManager.bench(title: "Slow DidEnterBackground", logIfLongerThan: 0.1, logInProduction: true) { NotificationCenter.default.post(name: .OWSApplicationDidEnterBackground, object: nil) } } @objc private func applicationWillResignActive(_ notification: Notification) { AssertIsOnMainThread() self.reportedApplicationState = .inactive BenchManager.bench(title: "Slow WillResignActive", logIfLongerThan: 0.1, logInProduction: true) { NotificationCenter.default.post(name: .OWSApplicationWillResignActive, object: nil) } } @objc private func applicationDidBecomeActive(_ notification: Notification) { AssertIsOnMainThread() self.reportedApplicationState = .active BenchManager.bench(title: "Slow DidBecomeActive", logIfLongerThan: 0.1, logInProduction: true) { NotificationCenter.default.post(name: .OWSApplicationDidBecomeActive, object: nil) } runAppActiveBlocks() } let isMainApp: Bool = true var isMainAppAndActive: Bool { UIApplication.shared.applicationState == .active } let isNSE: Bool = false let isRTL: Bool = UIApplication.shared.userInterfaceLayoutDirection == .rightToLeft var statusBarHeight: CGFloat { UIApplication.shared.statusBarFrame.size.height } func isInBackground() -> Bool { reportedApplicationState == .background } func isAppForegroundAndActive() -> Bool { reportedApplicationState == .active } func beginBackgroundTask(with expirationHandler: @escaping BackgroundTaskExpirationHandler) -> UIBackgroundTaskIdentifier { UIApplication.shared.beginBackgroundTask(expirationHandler: expirationHandler) } func endBackgroundTask(_ backgroundTaskIdentifier: UIBackgroundTaskIdentifier) { UIApplication.shared.endBackgroundTask(backgroundTaskIdentifier) } func ensureSleepBlocking(_ shouldBeBlocking: Bool, blockingObjectsDescription: String) { if UIApplication.shared.isIdleTimerDisabled != shouldBeBlocking { if shouldBeBlocking { Logger.info("Blocking sleep because of: \(blockingObjectsDescription)") } else { Logger.info("Unblocking sleep.") } } UIApplication.shared.isIdleTimerDisabled = shouldBeBlocking } func frontmostViewController() -> UIViewController? { UIApplication.shared.frontmostViewControllerIgnoringAlerts } func openSystemSettings() { UIApplication.shared.openSystemSettings() } func open(_ url: URL, completion: ((Bool) -> Void)? = nil) { UIApplication.shared.open(url, completionHandler: completion) } var isRunningTests: Bool { getenv("runningTests_dontStartApp") != nil } var frame: CGRect { self.mainWindow?.frame ?? .zero } var interfaceOrientation: UIInterfaceOrientation { AssertIsOnMainThread() return UIApplication.shared.statusBarOrientation } var mainWindow: UIWindow? private var appActiveBlocks = [AppActiveBlock]() func runNowOrWhenMainAppIsActive(_ block: @escaping AppActiveBlock) { DispatchMainThreadSafe { if self.isMainAppAndActive { block() return } self.appActiveBlocks.append(block) } } private func runAppActiveBlocks() { AssertIsOnMainThread() let appActiveBlocks = self.appActiveBlocks self.appActiveBlocks = [] for block in appActiveBlocks { block() } } func appDocumentDirectoryPath() -> String { FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last!.path } func appSharedDataDirectoryPath() -> String { FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: TSConstants.applicationGroup)!.path } func appDatabaseBaseDirectoryPath() -> String { appSharedDataDirectoryPath() } func appUserDefaults() -> UserDefaults { UserDefaults(suiteName: TSConstants.applicationGroup)! } func canPresentNotifications() -> Bool { true } let shouldProcessIncomingMessages: Bool = true let hasUI: Bool = true var debugLogsDirPath: String { DebugLogger.mainAppDebugLogsDirPath } func resetAppDataAndExit() -> Never { SignalApp.resetAppDataAndExit() } }