blob: f3b922654310c2fd1cddf4f1cb87911dc6ee70ef [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// 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"
#include "base/ios/ios_util.h"
#include "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/browser/ui/main/scene_state.h"
#import "ios/chrome/browser/ui/main/scene_state_observer.h"
#import "ios/chrome/browser/ui/safe_mode/safe_mode_coordinator.h"
#import "ios/chrome/browser/ui/scoped_ui_blocker/scoped_ui_blocker.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface SafeModeAppAgent () <SafeModeCoordinatorDelegate,
AppStateObserver,
SceneStateObserver> {
// Multiwindow UI blocker used when safe mode is active to only show the safe
// mode UI on one window.
std::unique_ptr<ScopedUIBlocker> _safeModeBlocker;
}
// This flag is set when the first scene has activated since the startup, and
// never reset.
@property(nonatomic, assign) BOOL firstSceneHasActivated;
// The app state the agent is connected to.
@property(nonatomic, weak, readonly) AppState* appState;
// Safe mode coordinator. If this is non-nil, the app is displaying the safe
// mode UI.
@property(nonatomic, strong) SafeModeCoordinator* safeModeCoordinator;
@end
@implementation SafeModeAppAgent
#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.
[self.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 (self.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 (self.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;
}
[self.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