diff --git a/Signal/AppLaunch/AppDelegate.swift b/Signal/AppLaunch/AppDelegate.swift index a1745758e3..dfaed68c7e 100644 --- a/Signal/AppLaunch/AppDelegate.swift +++ b/Signal/AppLaunch/AppDelegate.swift @@ -369,13 +369,14 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { } } - private lazy var screenLockUI = ScreenLockUI(appReadiness: appReadiness) - private func configureGlobalUI(in window: UIWindow) { + let screenLockUI = AppEnvironment.shared.screenLockUI + let windowManager = AppEnvironment.shared.windowManagerRef + Theme.setupSignalAppearance() screenLockUI.setupWithRootWindow(window) - AppEnvironment.shared.windowManagerRef.setupWithRootWindow(window, screenBlockingWindow: screenLockUI.screenBlockingWindow) + windowManager.setupWithRootWindow(window, screenBlockingWindow: screenLockUI.screenBlockingWindow) screenLockUI.startObserving() } @@ -1770,15 +1771,23 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { } let isVideo = isVideoCall(intent) - Task { @MainActor [appReadiness, screenLockUI] in + Task { @MainActor [appReadiness] in do { try await appReadiness.waitForAppReady() + } catch { + return + } + + let callService = AppEnvironment.shared.callService! + let screenLockUI = AppEnvironment.shared.screenLockUI + let tsAccountManager = DependenciesBridge.shared.tsAccountManager + + do { try await screenLockUI.waitForScreenUnlockThrowingPrevious() } catch { return } - let tsAccountManager = DependenciesBridge.shared.tsAccountManager guard tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegistered else { Logger.warn("Ignoring user activity; not registered.") return @@ -1796,7 +1805,6 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { // * It can be received if the user taps the "video" button for a contact // in the contacts app. If so, the correct response is to try to initiate a // new call to that user - unless there is another call in progress. - let callService = AppEnvironment.shared.callService! if let currentCall = callService.callServiceState.currentCall { if isVideo, case .individual = currentCall.mode, currentCall.mode.matches(callTarget) { Logger.info("Upgrading existing call to video") @@ -1933,12 +1941,18 @@ extension AppDelegate: UNUserNotificationCenterDelegate { withCompletionHandler completionHandler: @escaping () -> Void, ) { let startDate = MonotonicDate() - Task { @MainActor [appReadiness, screenLockUI] () -> Void in + Task { @MainActor [appReadiness] () -> Void in defer { completionHandler() } - try await self.appReadiness.waitForAppReady() + do { + try await self.appReadiness.waitForAppReady() + } catch { + return + } + let screenLockUI = AppEnvironment.shared.screenLockUI let backgroundMessageFetcherFactory = DependenciesBridge.shared.backgroundMessageFetcherFactory + let backgroundMessageFetcher = backgroundMessageFetcherFactory.buildFetcher() // So that we open up a connection for replies. await backgroundMessageFetcher.start() diff --git a/Signal/AppLaunch/AppEnvironment.swift b/Signal/AppLaunch/AppEnvironment.swift index db116323f0..985f026649 100644 --- a/Signal/AppLaunch/AppEnvironment.swift +++ b/Signal/AppLaunch/AppEnvironment.swift @@ -22,12 +22,12 @@ public class AppEnvironment: NSObject { @MainActor var ownedObjects = [AnyObject]() + let cvAudioPlayerRef: CVAudioPlayer let deviceTransferServiceRef: DeviceTransferService let pushRegistrationManagerRef: PushRegistrationManager - - let cvAudioPlayerRef = CVAudioPlayer() - let speechManagerRef = SpeechManager() - let windowManagerRef = WindowManager() + let screenLockUI: ScreenLockUI + let speechManagerRef: SpeechManager + let windowManagerRef: WindowManager private(set) var appIconBadgeUpdater: AppIconBadgeUpdater! private(set) var avatarHistoryManager: AvatarHistoryManager! @@ -44,8 +44,12 @@ public class AppEnvironment: NSObject { private var registrationIdMismatchManager: RegistrationIdMismatchManager! init(appReadiness: AppReadiness, deviceTransferService: DeviceTransferService) { + self.cvAudioPlayerRef = CVAudioPlayer() self.deviceTransferServiceRef = deviceTransferService + self.screenLockUI = ScreenLockUI(appReadiness: appReadiness) self.pushRegistrationManagerRef = PushRegistrationManager(appReadiness: appReadiness) + self.speechManagerRef = SpeechManager() + self.windowManagerRef = WindowManager() super.init() diff --git a/Signal/Backups/BackupRecordKeyViewController.swift b/Signal/Backups/BackupRecordKeyViewController.swift index d6d64392cd..464ceed4bb 100644 --- a/Signal/Backups/BackupRecordKeyViewController.swift +++ b/Signal/Backups/BackupRecordKeyViewController.swift @@ -77,6 +77,9 @@ class BackupRecordKeyViewController: OWSViewController, OWSNavigationChildContro override func viewDidLoad() { super.viewDidLoad() + let screenLockUI = AppEnvironment.shared.screenLockUI + screenLockUI.sensitiveContentDidLoad(inViewController: self) + view.backgroundColor = .Signal.groupedBackground if let onBackPressedBlock { diff --git a/Signal/Backups/EnterAccountEntropyPoolViewController.swift b/Signal/Backups/EnterAccountEntropyPoolViewController.swift index 23d0b32ff7..15501f0703 100644 --- a/Signal/Backups/EnterAccountEntropyPoolViewController.swift +++ b/Signal/Backups/EnterAccountEntropyPoolViewController.swift @@ -51,6 +51,9 @@ class EnterAccountEntropyPoolViewController: OWSViewController { override func viewDidLoad() { super.viewDidLoad() + let screenLockUI = AppEnvironment.shared.screenLockUI + screenLockUI.sensitiveContentDidLoad(inViewController: self) + view.backgroundColor = colorConfig.background navigationItem.rightBarButtonItem = UIBarButtonItem( title: CommonStrings.nextButton, diff --git a/Signal/src/ViewControllers/AppSettings/Payments/PaymentsViewPassphraseGridViewController.swift b/Signal/src/ViewControllers/AppSettings/Payments/PaymentsViewPassphraseGridViewController.swift index 64cd36de8a..8d1d979001 100644 --- a/Signal/src/ViewControllers/AppSettings/Payments/PaymentsViewPassphraseGridViewController.swift +++ b/Signal/src/ViewControllers/AppSettings/Payments/PaymentsViewPassphraseGridViewController.swift @@ -25,6 +25,9 @@ class PaymentsViewPassphraseGridViewController: OWSViewController { override func viewDidLoad() { super.viewDidLoad() + let screenLockUI = AppEnvironment.shared.screenLockUI + screenLockUI.sensitiveContentDidLoad(inViewController: self) + title = OWSLocalizedString( "SETTINGS_PAYMENTS_VIEW_PASSPHRASE_TITLE", comment: "Title for the 'view payments passphrase' view of the app settings.", diff --git a/Signal/util/ScreenLockUI.swift b/Signal/util/ScreenLockUI.swift index de1f4d1c5f..19f198e139 100644 --- a/Signal/util/ScreenLockUI.swift +++ b/Signal/util/ScreenLockUI.swift @@ -124,7 +124,7 @@ class ScreenLockUI { self.appReadiness = appReadiness } - // MARK: - Public + // MARK: - func setupWithRootWindow(_ rootWindow: UIWindow) { AssertIsOnMainThread() @@ -180,6 +180,21 @@ class ScreenLockUI { } } + // MARK: - Sensitive Content + + /// Tracks view controllers displaying "sensitive content" that should be + /// hidden from the App Switcher. + private var sensitiveContentViewControllers: WeakArray = [] + + @MainActor + func sensitiveContentDidLoad(inViewController viewController: UIViewController) { + if sensitiveContentViewControllers.contains(where: { $0 === viewController }) { + return + } + + sensitiveContentViewControllers.append(viewController) + } + // MARK: - UI private func updateScreenBlockingWindowWithUIState(_ uiState: ScreenLockViewController.UIState) { @@ -251,7 +266,16 @@ class ScreenLockUI { return .none } - guard SSKEnvironment.shared.preferencesRef.isScreenSecurityEnabled else { + // Cull any "sensitive content" view controllers that aren't actually + // presenting content. (Their presence here may suggest a retain cycle.) + sensitiveContentViewControllers.removeAll(where: { viewController in + !viewController.isViewLoaded || viewController.view.window == nil + }) + + guard + SSKEnvironment.shared.preferencesRef.isScreenSecurityEnabled + || sensitiveContentViewControllers.elements.count > 0 + else { return .none } diff --git a/SignalServiceKit/Util/Weak.swift b/SignalServiceKit/Util/Weak.swift index c3a8798cb1..8cc7a0aace 100644 --- a/SignalServiceKit/Util/Weak.swift +++ b/SignalServiceKit/Util/Weak.swift @@ -50,6 +50,16 @@ public struct WeakArray { } } + public func contains(where predictate: (Element) -> Bool) -> Bool { + return array.contains { weakElement in + if let element = weakElement.value, predictate(element) { + return true + } + + return false + } + } + public mutating func cullExpired() { array.removeAll { weakBox in weakBox.value == nil