| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/chrome/app/safe_mode_app_state_agent.h" |
| |
| #import "base/ios/ios_util.h" |
| #import "base/version.h" |
| #import "ios/chrome/app/application_delegate/app_state.h" |
| #import "ios/chrome/app/application_delegate/app_state_observer.h" |
| #import "ios/chrome/app/safe_mode_app_state_agent+private.h" |
| #import "ios/chrome/browser/safe_mode/ui_bundled/safe_mode_coordinator.h" |
| #import "ios/chrome/browser/shared/coordinator/scene/scene_state.h" |
| #import "ios/chrome/browser/shared/coordinator/scene/scene_state_observer.h" |
| #import "ios/chrome/browser/ui/scoped_ui_blocker/scoped_ui_blocker.h" |
| |
| @implementation SafeModeAppAgent { |
| // Multiwindow UI blocker used when safe mode is active to only show the safe |
| // mode UI on one window. |
| std::unique_ptr<ScopedUIBlocker> _safeModeBlocker; |
| |
| // The app state the agent is connected to. |
| __weak AppState* _appState; |
| } |
| |
| #pragma mark - AppStateAgent |
| |
| - (void)setAppState:(AppState*)appState { |
| // This should only be called once! |
| DCHECK(!_appState); |
| |
| _appState = appState; |
| [_appState addObserver:self]; |
| } |
| |
| #pragma mark - SafeModeCoordinatorDelegate Implementation |
| |
| - (void)coordinatorDidExitSafeMode:(SafeModeCoordinator*)coordinator { |
| DCHECK(coordinator); |
| [self stopSafeMode]; |
| // Transition out of Safe Mode init stage to the next stage. Tell the appState |
| // that the app is resuming from safe mode. |
| _appState.resumingFromSafeMode = YES; |
| [_appState queueTransitionToNextInitStage]; |
| } |
| |
| #pragma mark - SceneStateObserver |
| |
| - (void)sceneState:(SceneState*)sceneState |
| transitionedToActivationLevel:(SceneActivationLevel)level { |
| // Don't try to trigger Safe Mode when the scene is not yet active on the |
| // foreground. |
| if (level < SceneActivationLevelForegroundActive) { |
| return; |
| } |
| // Don't try to trigger Safe Mode when the app has already passed the safe |
| // mode stage when the scene transitions to foreground. If the init stage is |
| // still Safe Mode at this moment it means that safe mode has to be triggered. |
| if (_appState.initStage != InitStageSafeMode) { |
| return; |
| } |
| // Don't try to show the safe mode UI on multiple scenes; one scene is |
| // sufficient. |
| if (self.firstSceneHasActivated) { |
| return; |
| } |
| self.firstSceneHasActivated = YES; |
| |
| [self startSafeMode:sceneState]; |
| } |
| |
| #pragma mark - AppStateObserver |
| |
| - (void)appState:(AppState*)appState |
| didTransitionFromInitStage:(InitStage)previousInitStage { |
| if (_appState.initStage != InitStageSafeMode) { |
| return; |
| } |
| // Iterate further in the init stages when safe mode isn't needed; stop |
| // and switch the app to safe mode otherwise. |
| if ([SafeModeCoordinator shouldStart]) { |
| return; |
| } |
| |
| [_appState queueTransitionToNextInitStage]; |
| } |
| |
| - (void)appState:(AppState*)appState sceneConnected:(SceneState*)sceneState { |
| [sceneState addObserver:self]; |
| } |
| |
| #pragma mark - Internals |
| |
| - (void)startSafeMode:(SceneState*)sceneState { |
| DCHECK(sceneState); |
| DCHECK(!_safeModeBlocker); |
| |
| self.safeModeCoordinator = |
| [[SafeModeCoordinator alloc] initWithWindow:sceneState.window]; |
| self.safeModeCoordinator.delegate = self; |
| |
| // Activate the main window, which will prompt the views to load. |
| [sceneState.window makeKeyAndVisible]; |
| |
| [self.safeModeCoordinator start]; |
| |
| if (base::ios::IsMultipleScenesSupported()) { |
| _safeModeBlocker = std::make_unique<ScopedUIBlocker>(sceneState); |
| } |
| } |
| |
| - (void)stopSafeMode { |
| if (_safeModeBlocker) { |
| _safeModeBlocker.reset(); |
| } |
| self.safeModeCoordinator = nil; |
| } |
| |
| @end |