Hide "sensitive" views from the App Switcher

This commit is contained in:
Sasha Weiss 2026-06-08 20:47:21 -07:00 committed by GitHub
parent 6be8862bdb
commit 30431a6354
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 75 additions and 14 deletions

View File

@ -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()

View File

@ -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()

View File

@ -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 {

View File

@ -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,

View File

@ -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.",

View File

@ -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<UIViewController> = []
@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
}

View File

@ -50,6 +50,16 @@ public struct WeakArray<Element> {
}
}
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