Signal-iOS/SignalServiceKit/Util/AppContext.swift
2024-05-24 14:39:57 -05:00

147 lines
5.6 KiB
Swift

//
// 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
}