// // Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import CoreGraphics import Foundation import UIKit public typealias BackgroundTaskExpirationHandler = () -> Void public typealias AppActiveBlock = () -> Void // TODO: Cleanup this protocol. It was ported from ObjC and is a mess. Many funcs within should be vars. @objc public protocol AppContext: NSObjectProtocol { @objc var isMainApp: Bool { get } @objc var isMainAppAndActive: Bool { get } var isNSE: Bool { get } /// Whether the user is using a right-to-left language like Arabic. var isRTL: Bool { get } @objc var isRunningTests: Bool { get } var mainWindow: UIWindow? { get set } var frame: CGRect { get } var interfaceOrientation: UIInterfaceOrientation { get } /// Unlike UIApplication.applicationState, this is thread-safe. /// It contains the "last known" application state. /// /// Because it is updated in response to "will/did-style" events, it is /// conservative and skews toward less-active and not-foreground: /// /// * It doesn't report "is active" until the app is active /// and reports "inactive" as soon as it _will become_ inactive. /// * It doesn't report "is foreground (but inactive)" until the app is /// foreground & inactive and reports "background" as soon as it _will /// enter_ background. /// /// This conservatism is useful, since we want to err on the side of /// caution when, for example, we do work that should only be done /// when the app is foreground and active. var reportedApplicationState: UIApplication.State { get } /// A convenience accessor for reportedApplicationState. /// /// This method is thread-safe. func isInBackground() -> Bool /// A convenience accessor for reportedApplicationState. /// /// This method is thread-safe. func isAppForegroundAndActive() -> Bool /// Should start a background task if isMainApp is YES. /// Should just return UIBackgroundTaskInvalid if isMainApp is NO. @objc(beginBackgroundTaskWithExpirationHandler:) func beginBackgroundTask(with expirationHandler: @escaping BackgroundTaskExpirationHandler) -> UIBackgroundTaskIdentifier /// Should be a NOOP if isMainApp is NO. @objc func endBackgroundTask(_ backgroundTaskIdentifier: UIBackgroundTaskIdentifier) /// Should be a NOOP if isMainApp is NO. func ensureSleepBlocking(_ shouldBeBlocking: Bool, blockingObjectsDescription: String) var statusBarHeight: CGFloat { get } /// Returns the VC that should be used to present alerts, modals, etc. func frontmostViewController() -> UIViewController? func openSystemSettings() func open(_ url: URL, completion: ((_ success: Bool) -> Void)?) func runNowOrWhenMainAppIsActive(_ block: @escaping AppActiveBlock) @objc var appLaunchTime: Date { get } /// Will be updated every time the app is foregrounded. var appForegroundTime: Date { get } @objc func appDocumentDirectoryPath() -> String @objc func appSharedDataDirectoryPath() -> String func appDatabaseBaseDirectoryPath() -> String func appUserDefaults() -> UserDefaults /// This method should only be called by the main app. func mainApplicationStateOnLaunch() -> UIApplication.State func canPresentNotifications() -> Bool var shouldProcessIncomingMessages: Bool { get } var hasUI: Bool { get } var debugLogsDirPath: String { get } /// WARNING: Resets all persisted app data. (main app only). /// /// App becomes unuseable. As of time of writing, the only option /// after doing this is to terminate the app and relaunch. func resetAppDataAndExit() -> Never } @available(swift, obsoleted: 1) @objc public class AppContextObjcBridge: NSObject { @objc public static let owsApplicationWillResignActiveNotification = Notification.Name.OWSApplicationWillResignActive.rawValue @objc public static let owsApplicationDidBecomeActiveNotification = Notification.Name.OWSApplicationDidBecomeActive.rawValue @objc public static func CurrentAppContext() -> any AppContext { SignalServiceKit.CurrentAppContext() } @objc public static func SetCurrentAppContext(_ appContext: any AppContext) { SignalServiceKit.SetCurrentAppContext(appContext) } override private init() {} } // These are fired whenever the corresponding "main app" or "app extension" // notification is fired. // // 1. This saves you the work of observing both. // 2. This allows us to ensure that any critical work (e.g. re-opening // databases) has been done before app re-enters foreground, etc. public extension Notification.Name { // TODO: Rename this to a more swift style name static let OWSApplicationDidEnterBackground = Notification.Name("OWSApplicationDidEnterBackgroundNotification") static let OWSApplicationWillEnterForeground = Notification.Name("OWSApplicationWillEnterForegroundNotification") static let OWSApplicationWillResignActive = Notification.Name("OWSApplicationWillResignActiveNotification") static let OWSApplicationDidBecomeActive = Notification.Name("OWSApplicationDidBecomeActiveNotification") } private var currentAppContext: (any AppContext)? public func CurrentAppContext() -> any AppContext { // Yuck, but the objc function that came before this function lied about // not being able to return nil so the entire app is already written // assuming this can't be nil though it always could have been. currentAppContext! } public func SetCurrentAppContext(_ appContext: any AppContext) { currentAppContext = appContext }