// Copyright 2012 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/main_controller.h"
#import "ios/chrome/app/main_controller_private.h"

#import <memory>

#import "base/feature_list.h"
#import "base/functional/callback.h"
#import "base/ios/ios_util.h"
#import "base/mac/bundle_locations.h"
#import "base/mac/foundation_util.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "base/path_service.h"
#import "base/strings/sys_string_conversions.h"
#import "components/component_updater/component_updater_service.h"
#import "components/component_updater/crl_set_remover.h"
#import "components/component_updater/installer_policies/autofill_states_component_installer.h"
#import "components/component_updater/installer_policies/on_device_head_suggest_component_installer.h"
#import "components/component_updater/installer_policies/optimization_hints_component_installer.h"
#import "components/component_updater/installer_policies/safety_tips_component_installer.h"
#import "components/component_updater/url_param_filter_remover.h"
#import "components/feature_engagement/public/event_constants.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/metrics/metrics_pref_names.h"
#import "components/metrics/metrics_service.h"
#import "components/password_manager/core/common/password_manager_features.h"
#import "components/password_manager/core/common/passwords_directory_util_ios.h"
#import "components/prefs/ios/pref_observer_bridge.h"
#import "components/prefs/pref_change_registrar.h"
#import "components/previous_session_info/previous_session_info.h"
#import "components/sync/base/features.h"
#import "components/sync/driver/sync_service.h"
#import "components/web_resource/web_resource_pref_names.h"
#import "ios/chrome/app/app_metrics_app_state_agent.h"
#import "ios/chrome/app/application_delegate/metrics_mediator.h"
#import "ios/chrome/app/application_storage_metrics.h"
#import "ios/chrome/app/blocking_scene_commands.h"
#import "ios/chrome/app/deferred_initialization_runner.h"
#import "ios/chrome/app/enterprise_app_agent.h"
#import "ios/chrome/app/fast_app_terminate_buildflags.h"
#import "ios/chrome/app/features.h"
#import "ios/chrome/app/feed_app_agent.h"
#import "ios/chrome/app/first_run_app_state_agent.h"
#import "ios/chrome/app/memory_monitor.h"
#import "ios/chrome/app/post_restore_app_agent.h"
#import "ios/chrome/app/safe_mode_app_state_agent.h"
#import "ios/chrome/app/spotlight/spotlight_manager.h"
#import "ios/chrome/app/startup/chrome_app_startup_parameters.h"
#import "ios/chrome/app/startup/chrome_main_starter.h"
#import "ios/chrome/app/startup/client_registration.h"
#import "ios/chrome/app/startup/ios_chrome_main.h"
#import "ios/chrome/app/startup/provider_registration.h"
#import "ios/chrome/app/startup/register_experimental_settings.h"
#import "ios/chrome/app/startup/setup_debugging.h"
#import "ios/chrome/app/startup_tasks.h"
#import "ios/chrome/app/tests_hook.h"
#import "ios/chrome/app/variations_app_state_agent.h"
#import "ios/chrome/browser/accessibility/window_accessibility_change_notifier_app_agent.h"
#import "ios/chrome/browser/application_context/application_context.h"
#import "ios/chrome/browser/browser_state/chrome_browser_state.h"
#import "ios/chrome/browser/browser_state/chrome_browser_state_manager.h"
#import "ios/chrome/browser/browser_state/chrome_browser_state_removal_controller.h"
#import "ios/chrome/browser/browsing_data/browsing_data_remover.h"
#import "ios/chrome/browser/browsing_data/browsing_data_remover_factory.h"
#import "ios/chrome/browser/browsing_data/sessions_storage_util.h"
#import "ios/chrome/browser/crash_report/crash_helper.h"
#import "ios/chrome/browser/crash_report/crash_keys_helper.h"
#import "ios/chrome/browser/crash_report/crash_loop_detection_util.h"
#import "ios/chrome/browser/crash_report/crash_report_helper.h"
#import "ios/chrome/browser/credential_provider/credential_provider_buildflags.h"
#import "ios/chrome/browser/download/download_directory_util.h"
#import "ios/chrome/browser/external_files/external_file_remover_factory.h"
#import "ios/chrome/browser/external_files/external_file_remover_impl.h"
#import "ios/chrome/browser/favicon/ios_chrome_favicon_loader_factory.h"
#import "ios/chrome/browser/feature_engagement/tracker_factory.h"
#import "ios/chrome/browser/first_run/first_run.h"
#import "ios/chrome/browser/flags/system_flags.h"
#import "ios/chrome/browser/mailto_handler/mailto_handler_service.h"
#import "ios/chrome/browser/mailto_handler/mailto_handler_service_factory.h"
#import "ios/chrome/browser/main/browser.h"
#import "ios/chrome/browser/main/browser_list.h"
#import "ios/chrome/browser/main/browser_list_factory.h"
#import "ios/chrome/browser/main/browser_provider.h"
#import "ios/chrome/browser/memory/memory_debugger_manager.h"
#import "ios/chrome/browser/metrics/first_user_action_recorder.h"
#import "ios/chrome/browser/metrics/incognito_usage_app_state_agent.h"
#import "ios/chrome/browser/metrics/window_configuration_recorder.h"
#import "ios/chrome/browser/omaha/omaha_service.h"
#import "ios/chrome/browser/passwords/password_manager_util_ios.h"
#import "ios/chrome/browser/paths/paths.h"
#import "ios/chrome/browser/promos_manager/promos_manager_factory.h"
#import "ios/chrome/browser/screenshot/screenshot_metrics_recorder.h"
#import "ios/chrome/browser/search_engines/extension_search_engine_data_updater.h"
#import "ios/chrome/browser/search_engines/search_engines_util.h"
#import "ios/chrome/browser/search_engines/template_url_service_factory.h"
#import "ios/chrome/browser/sessions/scene_util.h"
#import "ios/chrome/browser/sessions/session_service_ios.h"
#import "ios/chrome/browser/share_extension/share_extension_service.h"
#import "ios/chrome/browser/share_extension/share_extension_service_factory.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_delegate.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/signin/authentication_service_delegate.h"
#import "ios/chrome/browser/signin/authentication_service_factory.h"
#import "ios/chrome/browser/snapshots/snapshot_browser_agent.h"
#import "ios/chrome/browser/snapshots/snapshot_cache.h"
#import "ios/chrome/browser/sync/sync_service_factory.h"
#import "ios/chrome/browser/ui/appearance/appearance_customization.h"
#import "ios/chrome/browser/ui/first_run/first_run_util.h"
#import "ios/chrome/browser/ui/main/browser_view_wrangler.h"
#import "ios/chrome/browser/ui/webui/chrome_web_ui_ios_controller_factory.h"
#import "ios/chrome/browser/url_loading/url_loading_params.h"
#import "ios/chrome/browser/web/certificate_policy_app_agent.h"
#import "ios/chrome/browser/web/session_state/web_session_state_cache.h"
#import "ios/chrome/browser/web/session_state/web_session_state_cache_factory.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/common/app_group/app_group_constants.h"
#import "ios/chrome/common/app_group/app_group_field_trial_version.h"
#import "ios/chrome/common/app_group/app_group_utils.h"
#import "ios/components/cookie_util/cookie_util.h"
#import "ios/net/cookies/cookie_store_ios.h"
#import "ios/net/empty_nsurlcache.h"
#import "ios/public/provider/chrome/browser/app_distribution/app_distribution_api.h"
#import "ios/public/provider/chrome/browser/overrides/overrides_api.h"
#import "ios/public/provider/chrome/browser/ui_utils/ui_utils_api.h"
#import "ios/public/provider/chrome/browser/user_feedback/user_feedback_api.h"
#import "ios/web/common/features.h"
#import "ios/web/public/webui/web_ui_ios_controller_factory.h"
#import "net/base/mac/url_conversions.h"
#import "services/network/public/cpp/shared_url_loader_factory.h"

#if BUILDFLAG(IOS_CREDENTIAL_PROVIDER_ENABLED)
#import "ios/chrome/app/credential_provider_migrator_app_agent.h"
#import "ios/chrome/browser/credential_provider/credential_provider_service_factory.h"
#import "ios/chrome/browser/credential_provider/credential_provider_support.h"
#import "ios/chrome/browser/credential_provider/credential_provider_util.h"
#endif

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

namespace {

#if BUILDFLAG(FAST_APP_TERMINATE_ENABLED)
// Skip chromeMain.reset() on shutdown, see crbug.com/1328891 for details.
BASE_FEATURE(kFastApplicationWillTerminate,
             "FastApplicationWillTerminate",
             base::FEATURE_DISABLED_BY_DEFAULT);
#endif  // BUILDFLAG(FAST_APP_TERMINATE_ENABLED)

// Constants for deferring resetting the startup attempt count (to give the app
// a little while to make sure it says alive).
NSString* const kStartupAttemptReset = @"StartupAttemptReset";

// Constants for deferring memory debugging tools startup.
NSString* const kMemoryDebuggingToolsStartup = @"MemoryDebuggingToolsStartup";

// Constant for deferring the cleanup of discarded sessions on disk.
NSString* const kCleanupDiscardedSessions = @"CleanupDiscardedSessions";

// Constants for deferring mailto handling initialization.
NSString* const kMailtoHandlingInitialization = @"MailtoHandlingInitialization";

// Constants for deferring saving field trial values
NSString* const kSaveFieldTrialValues = @"SaveFieldTrialValues";

// Constants for deferred check if it is necessary to send pings to
// Chrome distribution related services.
NSString* const kSendInstallPingIfNecessary = @"SendInstallPingIfNecessary";

// Constants for deferred deletion of leftover user downloaded files.
NSString* const kDeleteDownloads = @"DeleteDownloads";

// Constants for deferred deletion of leftover temporary passwords files.
NSString* const kDeleteTempPasswords = @"DeleteTempPasswords";

// Constants for deferred UMA logging of existing Siri User shortcuts.
NSString* const kLogSiriShortcuts = @"LogSiriShortcuts";

// Constants for deferred sending of queued feedback.
NSString* const kSendQueuedFeedback = @"SendQueuedFeedback";

// Constants for deferring the upload of crash reports.
NSString* const kUploadCrashReports = @"UploadCrashReports";

// Constants for deferring the cleanup of snapshots on disk.
NSString* const kCleanupSnapshots = @"CleanupSnapshots";

// Constants for deferring startup Spotlight bookmark indexing.
NSString* const kStartSpotlightBookmarksIndexing =
    @"StartSpotlightBookmarksIndexing";

// Constants for deferring the enterprise managed device check.
NSString* const kEnterpriseManagedDeviceCheck = @"EnterpriseManagedDeviceCheck";

// Constants for deferred deletion of leftover session state files.
NSString* const kPurgeWebSessionStates = @"PurgeWebSessionStates";

// Constants for deferred favicons clean up.
NSString* const kFaviconsCleanup = @"FaviconsCleanup";

// The minimum amount of time (2 weeks in seconds) between calculating and
// logging metrics about the amount of device storage space used by Chrome.
const NSTimeInterval kMinimumTimeBetweenDocumentsSizeLogging =
    60.0 * 60.0 * 24.0 * 14.0;

// Adapted from chrome/browser/ui/browser_init.cc.
void RegisterComponentsForUpdate() {
  component_updater::ComponentUpdateService* cus =
      GetApplicationContext()->GetComponentUpdateService();
  DCHECK(cus);
  base::FilePath path;
  const bool success = base::PathService::Get(ios::DIR_USER_DATA, &path);
  DCHECK(success);
  // Clean up any legacy CRLSet on the local disk - CRLSet used to be shipped
  // as a component on iOS but is not anymore.
  component_updater::DeleteLegacyCRLSet(path);
  component_updater::DeleteUrlParamFilter(path);

  RegisterOnDeviceHeadSuggestComponent(
      cus, GetApplicationContext()->GetApplicationLocale());
  RegisterSafetyTipsComponent(cus);
  RegisterAutofillStatesComponent(cus,
                                  GetApplicationContext()->GetLocalState());
  RegisterOptimizationHintsComponent(cus);
}

// The delay, in seconds, for cleaning external files.
const int kExternalFilesCleanupDelaySeconds = 60;

// Delegate for the AuthenticationService.
class MainControllerAuthenticationServiceDelegate
    : public AuthenticationServiceDelegate {
 public:
  MainControllerAuthenticationServiceDelegate(
      ChromeBrowserState* browser_state,
      id<BrowsingDataCommands> dispatcher);

  MainControllerAuthenticationServiceDelegate(
      const MainControllerAuthenticationServiceDelegate&) = delete;
  MainControllerAuthenticationServiceDelegate& operator=(
      const MainControllerAuthenticationServiceDelegate&) = delete;

  ~MainControllerAuthenticationServiceDelegate() override;

  // AuthenticationServiceDelegate implementation.
  void ClearBrowsingData(ProceduralBlock completion) override;

 private:
  ChromeBrowserState* browser_state_ = nullptr;
  __weak id<BrowsingDataCommands> dispatcher_ = nil;
};

MainControllerAuthenticationServiceDelegate::
    MainControllerAuthenticationServiceDelegate(
        ChromeBrowserState* browser_state,
        id<BrowsingDataCommands> dispatcher)
    : browser_state_(browser_state), dispatcher_(dispatcher) {}

MainControllerAuthenticationServiceDelegate::
    ~MainControllerAuthenticationServiceDelegate() = default;

void MainControllerAuthenticationServiceDelegate::ClearBrowsingData(
    ProceduralBlock completion) {
  [dispatcher_
      removeBrowsingDataForBrowserState:browser_state_
                             timePeriod:browsing_data::TimePeriod::ALL_TIME
                             removeMask:BrowsingDataRemoveMask::REMOVE_ALL
                        completionBlock:completion];
}

}  // namespace

@interface MainController () <PrefObserverDelegate,
                              BlockingSceneCommands,
                              SceneStateObserver> {
  IBOutlet UIWindow* _window;

  // The object that drives the Chrome startup/shutdown logic.
  std::unique_ptr<IOSChromeMain> _chromeMain;

  // True if the current session began from a cold start. False if the app has
  // entered the background at least once since start up.
  BOOL _isColdStart;

  // An object to record metrics related to the user's first action.
  std::unique_ptr<FirstUserActionRecorder> _firstUserActionRecorder;

  // Bridge to listen to pref changes.
  std::unique_ptr<PrefObserverBridge> _localStatePrefObserverBridge;

  // Registrar for pref changes notifications to the local state.
  PrefChangeRegistrar _localStatePrefChangeRegistrar;

  // Updates data about the current default search engine to be accessed in
  // extensions.
  std::unique_ptr<ExtensionSearchEngineDataUpdater>
      _extensionSearchEngineDataUpdater;

  // The class in charge of showing/hiding the memory debugger when the
  // appropriate pref changes.
  MemoryDebuggerManager* _memoryDebuggerManager;

  // Responsible for indexing chrome links (such as bookmarks, most likely...)
  // in system Spotlight index.
  SpotlightManager* _spotlightManager;

  // Cached launchOptions from -didFinishLaunchingWithOptions.
  NSDictionary* _launchOptions;

  // Variable backing metricsMediator property.
  __weak MetricsMediator* _metricsMediator;

  WindowConfigurationRecorder* _windowConfigurationRecorder;

  // Handler for the startup tasks, deferred or not.
  StartupTasks* _startupTasks;
}

// Handles collecting metrics on user triggered screenshots
@property(nonatomic, strong)
    ScreenshotMetricsRecorder* screenshotMetricsRecorder;
// Cleanup snapshots on disk.
- (void)cleanupSnapshots;
// Cleanup discarded sessions on disk.
- (void)cleanupDiscardedSessions;
// Sends any feedback that happens to still be on local storage.
- (void)sendQueuedFeedback;
// Called whenever an orientation change is received.
- (void)orientationDidChange:(NSNotification*)notification;
// Register to receive orientation change notification to update crash keys.
- (void)registerForOrientationChangeNotifications;
// Asynchronously creates the pref observers.
- (void)schedulePrefObserverInitialization;
// Asynchronously schedules pings to distribution services.
- (void)scheduleAppDistributionPings;
// Asynchronously schedule the init of the memoryDebuggerManager.
- (void)scheduleMemoryDebuggingTools;
// Asynchronously kick off regular free memory checks.
- (void)startFreeMemoryMonitoring;
// Asynchronously schedules the reset of the failed startup attempt counter.
- (void)scheduleStartupAttemptReset;
// Asynchronously schedules the upload of crash reports.
- (void)scheduleCrashReportUpload;
// Asynchronously schedules the cleanup of discarded session files on disk.
- (void)scheduleDiscardedSessionsCleanup;
// Asynchronously schedules the cleanup of snapshots on disk.
- (void)scheduleSnapshotsCleanup;
// Schedules various cleanup tasks that are performed after launch.
- (void)scheduleStartupCleanupTasks;
// Schedules various tasks to be performed after the application becomes active.
- (void)scheduleLowPriorityStartupTasks;
// Schedules tasks that require a fully-functional BVC to be performed.
- (void)scheduleTasksRequiringBVCWithBrowserState;
// Schedules the deletion of user downloaded files that might be leftover
// from the last time Chrome was run.
- (void)scheduleDeleteTempDownloadsDirectory;
// Schedule the deletion of the temporary passwords files that might
// be left over from incomplete export operations.
- (void)scheduleDeleteTempPasswordsDirectory;
// Crashes the application if requested.
- (void)crashIfRequested;
// Performs synchronous browser state initialization steps.
- (void)initializeBrowserState:(ChromeBrowserState*)browserState;
// Initializes the application to the minimum initialization needed in all
// cases.
- (void)startUpBrowserBasicInitialization;
//  Initializes the browser objects for the background handlers.
- (void)startUpBrowserBackgroundInitialization;
// Initializes the browser objects for the browser UI (e.g., the browser
// state).
- (void)startUpBrowserForegroundInitialization;
@end

@implementation MainController

// Defined by public protocols.
// - BrowserLauncher
@synthesize launchOptions = _launchOptions;
// - StartupInformation
@synthesize isColdStart = _isColdStart;
@synthesize appLaunchTime = _appLaunchTime;
@synthesize isFirstRun = _isFirstRun;
@synthesize didFinishLaunchingTime = _didFinishLaunchingTime;
@synthesize firstSceneConnectionTime = _firstSceneConnectionTime;

#pragma mark - Application lifecycle

- (instancetype)init {
  if ((self = [super init])) {
    _isFirstRun = ShouldPresentFirstRunExperience();
    _startupTasks = [[StartupTasks alloc] init];
  }
  return self;
}

- (void)dealloc {
  [NSObject cancelPreviousPerformRequestsWithTarget:self];
}

- (void)startUpBrowserBasicInitialization {
  _appLaunchTime = IOSChromeMain::StartTime();
  _isColdStart = YES;
  if (@available(iOS 15, *)) {
    UMA_HISTOGRAM_BOOLEAN("IOS.Process.ActivePrewarm",
                          base::ios::IsApplicationPreWarmed());
  }

  [SetupDebugging setUpDebuggingOptions];

  // Register all providers before calling any Chromium code.
  [ProviderRegistration registerProviders];

  // Start dispatching for blocking UI commands.
  [self.appState.appCommandDispatcher
      startDispatchingToTarget:self
                   forProtocol:@protocol(BlockingSceneCommands)];
  [self.appState.appCommandDispatcher
      startDispatchingToTarget:self
                   forProtocol:@protocol(BrowsingDataCommands)];
}

- (void)startUpBrowserBackgroundInitialization {
  DCHECK(self.appState.initStage > InitStageSafeMode);

  NSBundle* baseBundle = base::mac::OuterBundle();
  base::mac::SetBaseBundleID(
      base::SysNSStringToUTF8([baseBundle bundleIdentifier]).c_str());

  // Register default values for experimental settings (Application Preferences)
  // and set the "Version" key in the UserDefaults.
  [RegisterExperimentalSettings
      registerExperimentalSettingsWithUserDefaults:[NSUserDefaults
                                                       standardUserDefaults]
                                            bundle:base::mac::
                                                       FrameworkBundle()];

  // Register all clients before calling any web code.
  [ClientRegistration registerClients];

  _chromeMain = [ChromeMainStarter startChromeMain];

  // Remove the extra browser states as Chrome iOS is single profile in M48+.
  ChromeBrowserStateRemovalController::GetInstance()
      ->RemoveBrowserStatesIfNecessary();

  ChromeBrowserState* chromeBrowserState = GetApplicationContext()
                                               ->GetChromeBrowserStateManager()
                                               ->GetLastUsedBrowserState();

  // Initialize and set the main browser state.
  [self initializeBrowserState:chromeBrowserState];
  self.appState.mainBrowserState = chromeBrowserState;

  // Give tests a chance to prepare for testing.
  tests_hook::SetUpTestsIfPresent();

  // Force an obvious initialization of the AuthenticationService. This must
  // be done before creation of the UI to ensure the service is initialised
  // before use (it is a security issue, so accessing the service CHECKs if
  // this is not the case). It is important to do this during background
  // initialization when the app is cold started directly into the background
  // because it is used by the DiscoverFeedService, which is started in the
  // background to perform background refresh. There is no downside to doing
  // this during background initialization when the app is launched into the
  // foreground.
  AuthenticationServiceFactory::CreateAndInitializeForBrowserState(
      self.appState.mainBrowserState,
      std::make_unique<MainControllerAuthenticationServiceDelegate>(
          self.appState.mainBrowserState, self));

  // Initialize the provider UI global state.
  ios::provider::InitializeUI();

  // If the user has interacted with the app, then start (or continue) watching
  // for crashes. Otherwise, do not watch for crashes.
  //
  // Depending on the client's ExtendedVariationsSafeMode experiment group (see
  // MaybeExtendVariationsSafeMode() in variations_field_trial_creator.cc for
  // more info), the signal to start watching for crashes may have occurred
  // earlier.
  //
  // TODO(b/184937096): Remove the below call to OnAppEnterForeground() if this
  // call is moved earlier for all clients. It is is being kept here for the
  // time being for the control group of the extended Variations Safe Mode
  // experiment.
  //
  // TODO(crbug/1232027): Stop watching for a crash if this is a background
  // fetch.
  if (_appState.userInteracted)
    GetApplicationContext()->GetMetricsService()->OnAppEnterForeground();

  web::WebUIIOSControllerFactory::RegisterFactory(
      ChromeWebUIIOSControllerFactory::GetInstance());

  [NSURLCache setSharedURLCache:[EmptyNSURLCache emptyNSURLCache]];

  ChromeBrowserState* browserState = self.appState.mainBrowserState;
  DCHECK(browserState);
  [self.appState
      addAgent:
          [[PostRestoreAppAgent alloc]
              initWithPromosManager:PromosManagerFactory::GetForBrowserState(
                                        browserState)
              authenticationService:AuthenticationServiceFactory::
                                        GetForBrowserState(browserState)
                         localState:GetApplicationContext()->GetLocalState()]];
}

// This initialization must happen before any windows are created.
- (void)startUpBeforeFirstWindowCreated {
  GetApplicationContext()->OnAppEnterForeground();

  // Although this duplicates some metrics_service startup logic also in
  // IOSChromeMain(), this call does additional work, checking for wifi-only
  // and setting up the required support structures.
  [_metricsMediator updateMetricsStateBasedOnPrefsUserTriggered:NO];

  // Crash the app during startup if requested but only after we have enabled
  // uploading crash reports.
  [self crashIfRequested];

  if (experimental_flags::MustClearApplicationGroupSandbox()) {
    // Clear the Application group sandbox if requested. This operation take
    // some time and will access the file system synchronously as the rest of
    // the startup sequence requires it to be completed before continuing.
    app_group::ClearAppGroupSandbox();
  }

  RegisterComponentsForUpdate();

#if !defined(NDEBUG)
  // Legacy code used GetLastUsedBrowserState() in this method. We changed it to
  // use self.appState.mainBrowserState instead. The DCHECK ensures that
  // invariant holds true.
  ChromeBrowserState* chromeBrowserState = GetApplicationContext()
                                               ->GetChromeBrowserStateManager()
                                               ->GetLastUsedBrowserState();
  DCHECK_EQ(chromeBrowserState, self.appState.mainBrowserState);
#endif  // !defined(NDEBUG)

  if (!base::ios::IsMultipleScenesSupported()) {
    NSSet<NSString*>* previousSessions =
        [PreviousSessionInfo sharedInstance].connectedSceneSessionsIDs;
    DCHECK(previousSessions.count <= 1);
    self.appState.previousSingleWindowSessionID = [previousSessions anyObject];
  }
  [[PreviousSessionInfo sharedInstance] resetConnectedSceneSessionIDs];

  // Send "Chrome Opened" event to the feature_engagement::Tracker on cold
  // start.
  feature_engagement::TrackerFactory::GetForBrowserState(
      self.appState.mainBrowserState)
      ->NotifyEvent(feature_engagement::events::kChromeOpened);

  _spotlightManager = [SpotlightManager
      spotlightManagerWithBrowserState:self.appState.mainBrowserState];

  ShareExtensionService* service =
      ShareExtensionServiceFactory::GetForBrowserState(
          self.appState.mainBrowserState);
  service->Initialize();

#if BUILDFLAG(IOS_CREDENTIAL_PROVIDER_ENABLED)
  if (IsCredentialProviderExtensionSupported()) {
    CredentialProviderServiceFactory::GetForBrowserState(
        self.appState.mainBrowserState);
  }
#endif

  _windowConfigurationRecorder = [[WindowConfigurationRecorder alloc] init];
}

// This initialization must only happen once there's at least one Chrome window
// open.
- (void)startUpAfterFirstWindowCreated {
  // "Low priority" tasks
  [_startupTasks registerForApplicationWillResignActiveNotification];
  [self registerForOrientationChangeNotifications];

  _launchOptions = nil;

  [self scheduleTasksRequiringBVCWithBrowserState];

  CustomizeUIAppearance();

  [self scheduleStartupCleanupTasks];
  [MetricsMediator
      logLaunchMetricsWithStartupInformation:self
                             connectedScenes:self.appState.connectedScenes];

  ios::provider::InstallOverrides();

  [self scheduleLowPriorityStartupTasks];

  // Now that everything is properly set up, run the tests.
  tests_hook::RunTestsIfPresent();

  self.screenshotMetricsRecorder = [[ScreenshotMetricsRecorder alloc] init];
  [self.screenshotMetricsRecorder startRecordingMetrics];
}

- (PostCrashAction)postCrashAction {
  if (self.appState.resumingFromSafeMode)
    return PostCrashAction::kShowSafeMode;

  if (GetApplicationContext()->WasLastShutdownClean())
    return PostCrashAction::kRestoreTabsCleanShutdown;

  if (crash_util::GetFailedStartupAttemptCount() >= 2) {
    return PostCrashAction::kShowNTPWithReturnToTab;
  }

  return PostCrashAction::kRestoreTabsUncleanShutdown;
}

- (void)startUpBrowserForegroundInitialization {
  // TODO(crbug/1232027): Determine whether Chrome needs to resume watching for
  // crashes.

  self.appState.postCrashAction = [self postCrashAction];
  [self startUpBeforeFirstWindowCreated];
  base::UmaHistogramEnumeration("Stability.IOS.PostCrashAction",
                                self.appState.postCrashAction);
}

- (void)initializeBrowserState:(ChromeBrowserState*)browserState {
  DCHECK(!browserState->IsOffTheRecord());
  search_engines::UpdateSearchEnginesIfNeeded(
      browserState->GetPrefs(),
      ios::TemplateURLServiceFactory::GetForBrowserState(browserState));
}

#pragma mark - AppStateObserver

- (void)appState:(AppState*)appState sceneConnected:(SceneState*)sceneState {
  [sceneState addObserver:self];
}

// Called when the first scene becomes active.
- (void)appState:(AppState*)appState
    firstSceneHasInitializedUI:(SceneState*)sceneState {
  DCHECK(self.appState.initStage > InitStageSafeMode);

  if (self.appState.initStage <= InitStageNormalUI) {
    return;
  }

  // TODO(crbug.com/1213955): Pass the scene to this method to make sure that
  // the chosen scene is initialized.
  [self startUpAfterFirstWindowCreated];
}

- (void)appState:(AppState*)appState
    didTransitionFromInitStage:(InitStage)previousInitStage {
  // TODO(crbug.com/1213955): Remove this once the bug fixed.
  if (previousInitStage == InitStageNormalUI &&
      appState.firstSceneHasInitializedUI) {
    [self startUpAfterFirstWindowCreated];
  }

  switch (appState.initStage) {
    case InitStageStart:
      [appState queueTransitionToNextInitStage];
      break;
    case InitStageBrowserBasic:
      [self startUpBrowserBasicInitialization];
      break;
    case InitStageSafeMode:
      [self addPostSafeModeAgents];
      break;
    case InitStageVariationsSeed:
      break;
    case InitStageBrowserObjectsForBackgroundHandlers:
      [self startUpBrowserBackgroundInitialization];
      [appState queueTransitionToNextInitStage];
      break;
    case InitStageEnterprise:
      break;
    case InitStageBrowserObjectsForUI:
      [self maybeContinueForegroundInitialization];
      break;
    case InitStageNormalUI:
      // Scene controllers use this stage to create the normal UI if needed.
      // There is no specific agent (other than SceneController) handling
      // this stage.
      [appState queueTransitionToNextInitStage];
      break;
    case InitStageFirstRun:
      break;
    case InitStageFinal:
      break;
  }
}

- (void)addPostSafeModeAgents {
  [self.appState addAgent:[[EnterpriseAppAgent alloc] init]];
  [self.appState addAgent:[[IncognitoUsageAppStateAgent alloc] init]];
  [self.appState addAgent:[[FirstRunAppAgent alloc] init]];
  [self.appState addAgent:[[CertificatePolicyAppAgent alloc] init]];
#if BUILDFLAG(IOS_CREDENTIAL_PROVIDER_ENABLED)
  [self.appState addAgent:[[CredentialProviderAppAgent alloc] init]];
#endif
}

#pragma mark - SceneStateObserver

- (void)sceneState:(SceneState*)sceneState
    transitionedToActivationLevel:(SceneActivationLevel)level {
  if (level == SceneActivationLevelUnattached) {
    [sceneState removeObserver:self];
  } else if (level > SceneActivationLevelBackground) {
    // Stop observing all scenes since we only needed to know when the app
    // (first scene) is about to go to the foreground.
    for (SceneState* scene in _appState.connectedScenes) {
      [scene removeObserver:self];
    }
    [self maybeContinueForegroundInitialization];
  }
}

#pragma mark - Property implementation.

- (void)setAppState:(AppState*)appState {
  DCHECK(!_appState);
  _appState = appState;
  [appState addObserver:self];

  // Create app state agents.
  [appState addAgent:[[AppMetricsAppStateAgent alloc] init]];
  [appState addAgent:[[SafeModeAppAgent alloc] init]];
  [appState addAgent:[[FeedAppAgent alloc] init]];
  [appState addAgent:[[VariationsAppStateAgent alloc] init]];

  // Create the window accessibility agent only when multiple windows are
  // possible.
  if (base::ios::IsMultipleScenesSupported()) {
    [appState addAgent:[[WindowAccessibilityChangeNotifierAppAgent alloc] init]];
  }
}

- (id<BrowserProviderInterface>)browserProviderInterface {
  if (self.appState.foregroundActiveScene) {
    return self.appState.foregroundActiveScene.browserProviderInterface;
  }
  NSArray<SceneState*>* connectedScenes = self.appState.connectedScenes;

  return connectedScenes.count == 0
             ? nil
             : connectedScenes[0].browserProviderInterface;
}

- (BOOL)isFirstLaunchAfterUpgrade {
  return [[PreviousSessionInfo sharedInstance] isFirstSessionAfterUpgrade];
}

#pragma mark - StartupInformation implementation.


- (FirstUserActionRecorder*)firstUserActionRecorder {
  return _firstUserActionRecorder.get();
}

- (void)resetFirstUserActionRecorder {
  _firstUserActionRecorder.reset();
}

- (void)expireFirstUserActionRecorderAfterDelay:(NSTimeInterval)delay {
  [self performSelector:@selector(expireFirstUserActionRecorder)
             withObject:nil
             afterDelay:delay];
}

- (void)activateFirstUserActionRecorderWithBackgroundTime:
    (NSTimeInterval)backgroundTime {
  base::TimeDelta delta = base::Seconds(backgroundTime);
  _firstUserActionRecorder.reset(new FirstUserActionRecorder(delta));
}

- (void)stopChromeMain {
  [_spotlightManager shutdown];
  _spotlightManager = nil;

  _extensionSearchEngineDataUpdater = nullptr;

  // _localStatePrefChangeRegistrar is observing the PrefService, which is owned
  // indirectly by _chromeMain (through the ChromeBrowserState).
  // Unregister the observer before the service is destroyed.
  _localStatePrefChangeRegistrar.RemoveAll();

  // Under the UIScene API, the scene delegate does not receive
  // sceneDidDisconnect: notifications on app termination. We mark remaining
  // connected scene states as disconnected in order to allow services to
  // properly unregister their observers and tear down remaining UI.
  for (SceneState* sceneState in self.appState.connectedScenes) {
    sceneState.activationLevel = SceneActivationLevelUnattached;
  }

#if BUILDFLAG(FAST_APP_TERMINATE_ENABLED)
  // _chromeMain.reset() is a blocking call that regularly causes
  // applicationWillTerminate to fail after a 5s delay. Experiment with skipping
  // this shutdown call. See: crbug.com/1328891
  if (base::FeatureList::IsEnabled(kFastApplicationWillTerminate)) {
    metrics::MetricsService* metrics =
        GetApplicationContext()->GetMetricsService();
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    static std::atomic<uint32_t> counter{metrics ? 2u : 1u};
    ProceduralBlock completionBlock = ^{
      if (!--counter) {
        dispatch_semaphore_signal(semaphore);
      }
    };
    [[SessionServiceIOS sharedService] shutdownWithCompletion:completionBlock];

    if (metrics) {
      metrics->Stop();
      // MetricsService::Stop() depends on a committed local state, and does so
      // asynchronously. To avoid losing metrics, this minimum wait is required.
      // This will introduce a wait that will likely be the source of a number
      // of watchdog kills, but it should still be fewer than the number of
      // kills `_chromeMain.reset()` is responsible for.
      GetApplicationContext()->GetLocalState()->CommitPendingWrite(
          {}, base::BindOnce(completionBlock));
      dispatch_time_t dispatchTime =
          dispatch_time(DISPATCH_TIME_NOW, 4 * NSEC_PER_SEC);
      dispatch_semaphore_wait(semaphore, dispatchTime);
    }

    return;
  }
#endif  // BUILDFLAG(FAST_APP_TERMINATE_ENABLED)

  _chromeMain.reset();
}

#pragma mark - Startup tasks

// Continues foreground initialization iff both the init stage and activation
// level are ready.
- (void)maybeContinueForegroundInitialization {
  if (self.appState.foregroundScenes.count > 0 &&
      self.appState.initStage == InitStageBrowserObjectsForUI) {
    DCHECK(self.appState.userInteracted);
    [self startUpBrowserForegroundInitialization];
    [self.appState queueTransitionToNextInitStage];
  }
}

- (void)sendQueuedFeedback {
  if (ios::provider::IsUserFeedbackSupported()) {
    [[DeferredInitializationRunner sharedInstance]
        enqueueBlockNamed:kSendQueuedFeedback
                    block:^{
                      ios::provider::UploadAllPendingUserFeedback();
                    }];
  }
}

- (void)orientationDidChange:(NSNotification*)notification {
  crash_keys::SetCurrentOrientation(GetInterfaceOrientation(),
                                    [[UIDevice currentDevice] orientation]);
}

- (void)registerForOrientationChangeNotifications {
  // Register device orientation. UI orientation will be registered by
  // each window BVC. These two events may be triggered independently.
  [[NSNotificationCenter defaultCenter]
      addObserver:self
         selector:@selector(orientationDidChange:)
             name:UIDeviceOrientationDidChangeNotification
           object:nil];
}

- (void)schedulePrefObserverInitialization {
  __weak MainController* weakSelf = self;
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kPrefObserverInit
                  block:^{
                    [weakSelf initializePrefObservers];
                  }];
}

- (void)initializePrefObservers {
  // Track changes to local state prefs.
  PrefService* localState = GetApplicationContext()->GetLocalState();
  _localStatePrefChangeRegistrar.Init(localState);
  _localStatePrefObserverBridge = std::make_unique<PrefObserverBridge>(self);
  _localStatePrefObserverBridge->ObserveChangesForPreference(
      metrics::prefs::kMetricsReportingEnabled,
      &_localStatePrefChangeRegistrar);

  // Calls the onPreferenceChanged function in case there was a change to the
  // observed preferences before the observer bridge was set up. However, if the
  // metrics reporting pref is still unset (has default value), then do not
  // call. This likely means that the user is still on the welcome screen during
  // the first run experience (FRE), and calling onPreferenceChanged here would
  // clear the provisional client ID (in
  // MetricsMediator::updateMetricsPrefsOnPermissionChange). The provisional
  // client ID is crucial for field trial assignment consistency between the
  // first session and follow-up sessions, and is promoted to be the real client
  // ID if the user enables metrics reporting in the FRE. Otherwise, it is
  // discarded, as would happen here if onPreferenceChanged was called while the
  // user was still on the welcome screen and did yet enable/disable metrics
  // reporting.
  if (!localState->FindPreference(metrics::prefs::kMetricsReportingEnabled)
           ->IsDefaultValue()) {
    [self onPreferenceChanged:metrics::prefs::kMetricsReportingEnabled];
  }

  // Track changes to default search engine.
  TemplateURLService* service =
      ios::TemplateURLServiceFactory::GetForBrowserState(
          self.appState.mainBrowserState);
  _extensionSearchEngineDataUpdater =
      std::make_unique<ExtensionSearchEngineDataUpdater>(service);
}

- (void)scheduleAppDistributionPings {
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kSendInstallPingIfNecessary
                  block:^{
                    auto URLLoaderFactory = self.appState.mainBrowserState
                                                ->GetSharedURLLoaderFactory();

                    const bool is_first_run = FirstRun::IsChromeFirstRun();
                    ios::provider::ScheduleAppDistributionNotifications(
                        URLLoaderFactory, is_first_run);

                    const base::Time install_date = base::Time::FromTimeT(
                        GetApplicationContext()->GetLocalState()->GetInt64(
                            metrics::prefs::kInstallDate));

                    ios::provider::InitializeFirebase(install_date,
                                                      is_first_run);
                  }];
}

- (void)scheduleStartupAttemptReset {
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kStartupAttemptReset
                  block:^{
                    crash_util::ResetFailedStartupAttemptCount();
                  }];
}

- (void)scheduleCrashReportUpload {
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kUploadCrashReports
                  block:^{
                    crash_helper::UploadCrashReports();
                  }];
}

- (void)scheduleDiscardedSessionsCleanup {
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kCleanupDiscardedSessions
                  block:^{
                    [self cleanupDiscardedSessions];
                  }];
}

- (void)scheduleSnapshotsCleanup {
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kCleanupSnapshots
                  block:^{
                    [self cleanupSnapshots];
                  }];
}

- (void)scheduleSessionStateCacheCleanup {
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kPurgeWebSessionStates
                  block:^{
                    WebSessionStateCache* cache =
                        WebSessionStateCacheFactory::GetForBrowserState(
                            self.appState.mainBrowserState);
                    [cache purgeUnassociatedData];
                  }];
}

- (void)scheduleStartupCleanupTasks {
  // Schedule the prefs observer init first to ensure kMetricsReportingEnabled
  // is synced before starting uploads.
  [self schedulePrefObserverInitialization];
  [self scheduleCrashReportUpload];

  // ClearSessionCookies() is not synchronous.
  if (cookie_util::ShouldClearSessionCookies()) {
    cookie_util::ClearSessionCookies(
        self.appState.mainBrowserState->GetOriginalChromeBrowserState());
    if (!(self.otrBrowser->GetWebStateList()->empty())) {
      cookie_util::ClearSessionCookies(
          self.appState.mainBrowserState->GetOffTheRecordChromeBrowserState());
    }
  }

  // Remove all discarded sessions from disk.
  [self scheduleDiscardedSessionsCleanup];

  // If the user chooses to restore their session, some cached snapshots and
  // session states may be needed. Otherwise, cleanup the snapshots and session
  // states
  [self scheduleSnapshotsCleanup];
  [self scheduleSessionStateCacheCleanup];
}

- (void)scheduleMemoryDebuggingTools {
  if (experimental_flags::IsMemoryDebuggingEnabled()) {
    __weak MainController* weakSelf = self;
    [[DeferredInitializationRunner sharedInstance]
        enqueueBlockNamed:kMemoryDebuggingToolsStartup
                    block:^{
                      [weakSelf initializedMemoryDebuggingTools];
                    }];
  }
}

- (void)initializedMemoryDebuggingTools {
  DCHECK(!_memoryDebuggerManager);
  DCHECK(experimental_flags::IsMemoryDebuggingEnabled());
  _memoryDebuggerManager = [[MemoryDebuggerManager alloc]
      initWithView:self.appState.foregroundActiveScene.window
             prefs:GetApplicationContext()->GetLocalState()];
}

- (void)initializeMailtoHandling {
  __weak __typeof(self) weakSelf = self;
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kMailtoHandlingInitialization
                  block:^{
                    __strong __typeof(weakSelf) strongSelf = weakSelf;
                    if (!strongSelf || !strongSelf.appState.mainBrowserState) {
                      return;
                    }
                    // Force the creation of the MailtoHandlerService.
                    MailtoHandlerServiceFactory::GetForBrowserState(
                        strongSelf.appState.mainBrowserState);
                  }];
}

// Schedule a call to `scheduleSaveFieldTrialValuesForExternals` for deferred
// execution. Externals can be extensions or 1st party apps.
- (void)scheduleSaveFieldTrialValuesForExternals {
  __weak __typeof(self) weakSelf = self;
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kSaveFieldTrialValues
                  block:^{
                    [weakSelf saveFieldTrialValuesForExtensions];
                    [weakSelf saveFieldTrialValuesForGroupApp];
                  }];
}

// Some experiments value may be useful for first-party applications, so save
// the value in the shared application group.
- (void)saveFieldTrialValuesForGroupApp {
  NSUserDefaults* sharedDefaults = app_group::GetCommonGroupUserDefaults();
  NSNumber* supportsShowDefaultBrowserPromo =
      @(base::FeatureList::IsEnabled(kDefaultBrowserIntentsShowSettings));

  NSDictionary* capabilities = @{
    app_group::
    kChromeShowDefaultBrowserPromoCapability : supportsShowDefaultBrowserPromo
  };
  [sharedDefaults setObject:capabilities
                     forKey:app_group::kChromeCapabilitiesPreference];
}

// Some extensions need the value of field trials but can't get them because the
// field trial infrastructure isn't in extensions. Save the necessary values to
// NSUserDefaults here.
- (void)saveFieldTrialValuesForExtensions {
  NSUserDefaults* sharedDefaults = app_group::GetGroupUserDefaults();

  NSNumber* credentialProviderExtensionPasswordNotesValue =
      [NSNumber numberWithBool:base::FeatureList::IsEnabled(
                                   syncer::kPasswordNotesWithBackup)];
  NSNumber* credentialProviderExtensionPasswordNotesVersion =
      [NSNumber numberWithInt:kCredentialProviderExtensionPasswordNotesVersion];

  // Add other field trial values here if they are needed by extensions.
  // The general format is
  // {
  //   name: {
  //     value: NSNumber bool,
  //     version: NSNumber int,
  //   }
  // }
  NSDictionary* fieldTrialValues = @{
    base::SysUTF8ToNSString(syncer::kPasswordNotesWithBackup.name) : @{
      kFieldTrialValueKey : credentialProviderExtensionPasswordNotesValue,
      kFieldTrialVersionKey : credentialProviderExtensionPasswordNotesVersion,
    },
  };
  [sharedDefaults setObject:fieldTrialValues
                     forKey:app_group::kChromeExtensionFieldTrialPreference];
}

// Schedules a call to `logIfEnterpriseManagedDevice` for deferred
// execution.
- (void)scheduleEnterpriseManagedDeviceCheck {
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kEnterpriseManagedDeviceCheck
                  block:^{
                    [self logIfEnterpriseManagedDevice];
                  }];
}

- (void)logIfEnterpriseManagedDevice {
  NSString* managedKey = @"com.apple.configuration.managed";
  BOOL isManagedDevice = [[NSUserDefaults standardUserDefaults]
                             dictionaryForKey:managedKey] != nil;

  base::UmaHistogramBoolean("EnterpriseCheck.IsManaged2", isManagedDevice);
}

- (void)startFreeMemoryMonitoring {
  // No need for a post-task or a deferred initialisation as the memory
  // monitoring already happens on a background sequence.
  StartFreeMemoryMonitor();
}

- (void)scheduleLowPriorityStartupTasks {
  [_startupTasks initializeOmaha];

  // Deferred tasks.
  [self scheduleMemoryDebuggingTools];
  [StartupTasks
      scheduleDeferredBrowserStateInitialization:self.appState
                                                     .mainBrowserState];
  [self sendQueuedFeedback];
  [self scheduleSpotlightResync];
  [self scheduleDeleteTempDownloadsDirectory];
  [self scheduleDeleteTempPasswordsDirectory];
  [self scheduleLogSiriShortcuts];
  [self scheduleStartupAttemptReset];
  [self startFreeMemoryMonitoring];
  [self scheduleAppDistributionPings];
  [self initializeMailtoHandling];
  [self scheduleSaveFieldTrialValuesForExternals];
  [self scheduleEnterpriseManagedDeviceCheck];
  [self scheduleFaviconsCleanup];
  [self scheduleLogDocumentsSize];
}

- (void)scheduleTasksRequiringBVCWithBrowserState {
  if (GetApplicationContext()->WasLastShutdownClean()) {
    // Delay the cleanup of the unreferenced files to not impact startup
    // performance.
    ExternalFileRemoverFactory::GetForBrowserState(
        self.appState.mainBrowserState)
        ->RemoveAfterDelay(base::Seconds(kExternalFilesCleanupDelaySeconds),
                           base::OnceClosure());
  }
}

- (void)scheduleDeleteTempDownloadsDirectory {
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kDeleteDownloads
                  block:^{
                    DeleteTempDownloadsDirectory();
                  }];
}

- (void)scheduleDeleteTempPasswordsDirectory {
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kDeleteTempPasswords
                  block:^{
                    password_manager::DeletePasswordsDirectory();
                  }];
}

- (void)scheduleLogSiriShortcuts {
  __weak StartupTasks* startupTasks = _startupTasks;
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kLogSiriShortcuts
                  block:^{
                    [startupTasks logSiriShortcuts];
                  }];
}

- (void)scheduleSpotlightResync {
  __weak SpotlightManager* spotlightManager = _spotlightManager;
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kStartSpotlightBookmarksIndexing
                  block:^{
                    [spotlightManager resyncIndex];
                  }];
}

- (void)scheduleFaviconsCleanup {
#if BUILDFLAG(IOS_CREDENTIAL_PROVIDER_ENABLED)
  __weak MainController* weakSelf = self;
  [[DeferredInitializationRunner sharedInstance]
      enqueueBlockNamed:kFaviconsCleanup
                  block:^{
                    [weakSelf performFaviconsCleanup];
                  }];
#endif
}

- (void)scheduleLogDocumentsSize {
  if (!base::FeatureList::IsEnabled(kLogApplicationStorageSizeMetrics)) {
    return;
  }

  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  NSDate* lastLogged = base::mac::ObjCCast<NSDate>(
      [defaults objectForKey:kLastApplicationStorageMetricsLogTime]);
  if (lastLogged && [[NSDate date] timeIntervalSinceDate:lastLogged] <
                        kMinimumTimeBetweenDocumentsSizeLogging) {
    return;
  }

  base::FilePath profilePath = self.appState.mainBrowserState->GetStatePath();
  LogApplicationStorageMetrics(profilePath);
}

- (void)expireFirstUserActionRecorder {
  // Clear out any scheduled calls to this method. For example, the app may have
  // been backgrounded before the `kFirstUserActionTimeout` expired.
  [NSObject
      cancelPreviousPerformRequestsWithTarget:self
                                     selector:@selector(
                                                  expireFirstUserActionRecorder)
                                       object:nil];

  if (_firstUserActionRecorder) {
    _firstUserActionRecorder->Expire();
    _firstUserActionRecorder.reset();
  }
}

- (void)crashIfRequested {
  if (experimental_flags::IsStartupCrashEnabled()) {
    // Flush out the value cached for crash_helper::SetEnabled().
    [[NSUserDefaults standardUserDefaults] synchronize];

    int* x = NULL;
    *x = 0;
  }
}

#pragma mark - Preferences Management

- (void)onPreferenceChanged:(const std::string&)preferenceName {
  // Turn on or off metrics & crash reporting when either preference changes.
  if (preferenceName == metrics::prefs::kMetricsReportingEnabled) {
    [_metricsMediator updateMetricsStateBasedOnPrefsUserTriggered:YES];
  }
}

#pragma mark - Helper methods backed by interfaces.

- (Browser*)mainBrowser {
  DCHECK(self.browserProviderInterface);
  return self.browserProviderInterface.mainBrowserProvider.browser;
}

- (Browser*)otrBrowser {
  DCHECK(self.browserProviderInterface);
  return self.browserProviderInterface.incognitoBrowserProvider.browser;
}

- (Browser*)currentBrowser {
  return self.browserProviderInterface.currentBrowserProvider.browser;
}

- (ChromeBrowserState*)currentBrowserState {
  if (!self.browserProviderInterface.currentBrowserProvider.browser) {
    return nullptr;
  }
  return self.browserProviderInterface.currentBrowserProvider.browser
      ->GetBrowserState();
}

- (void)cleanupSnapshots {
  // TODO(crbug.com/1116496): Browsers for disconnected scenes are not in the
  // BrowserList, so this may not reach all folders.
  BrowserList* browser_list =
      BrowserListFactory::GetForBrowserState(self.appState.mainBrowserState);
  for (Browser* browser : browser_list->AllRegularBrowsers()) {
    SnapshotBrowserAgent::FromBrowser(browser)->PerformStorageMaintenance();
  }
  for (Browser* browser : browser_list->AllIncognitoBrowsers()) {
    SnapshotBrowserAgent::FromBrowser(browser)->PerformStorageMaintenance();
  }
}

- (void)cleanupDiscardedSessions {
  NSArray<NSString*>* sessionIDs =
      sessions_storage_util::GetDiscardedSessions();
  if (!sessionIDs)
    return;
  BrowsingDataRemoverFactory::GetForBrowserState(
      self.appState.mainBrowserState->GetOriginalChromeBrowserState())
      ->RemoveSessionsData(sessionIDs);
  BrowsingDataRemoverFactory::GetForBrowserState(
      self.appState.mainBrowserState->GetOffTheRecordChromeBrowserState())
      ->RemoveSessionsData(sessionIDs);
  sessions_storage_util::ResetDiscardedSessions();
}

#pragma mark - BrowsingDataCommands

- (void)removeBrowsingDataForBrowserState:(ChromeBrowserState*)browserState
                               timePeriod:(browsing_data::TimePeriod)timePeriod
                               removeMask:(BrowsingDataRemoveMask)removeMask
                          completionBlock:(ProceduralBlock)completionBlock {
  BOOL willShowActivityIndicator =
      !browserState->IsOffTheRecord() &&
      IsRemoveDataMaskSet(removeMask, BrowsingDataRemoveMask::REMOVE_SITE_DATA);
  BOOL didShowActivityIndicator = NO;

  for (SceneState* sceneState in self.appState.connectedScenes) {
    // Assumes all scenes share `browserState`.
    id<BrowserProviderInterface> browserProviderInterface =
        sceneState.browserProviderInterface;
    if (willShowActivityIndicator) {
      // Show activity overlay so users know that clear browsing data is in
      // progress.
      if (browserProviderInterface.mainBrowserProvider.browser) {
        didShowActivityIndicator = YES;
        id<BrowserCoordinatorCommands> handler =
            HandlerForProtocol(browserProviderInterface.mainBrowserProvider
                                   .browser->GetCommandDispatcher(),
                               BrowserCoordinatorCommands);
        [handler showActivityOverlay];
      }
    }
  }

  auto removalCompletion = ^{
    // Activates browsing and enables web views.
    // Must be called only on the main thread.
    DCHECK([NSThread isMainThread]);
    for (SceneState* sceneState in self.appState.connectedScenes) {
      // Assumes all scenes share `browserState`.
      id<BrowserProviderInterface> browserProviderInterface =
          sceneState.browserProviderInterface;

      if (willShowActivityIndicator) {
        // User interaction still needs to be disabled as a way to
        // force reload all the web states and to reset NTPs.
        browserProviderInterface.mainBrowserProvider.userInteractionEnabled =
            NO;
        browserProviderInterface.incognitoBrowserProvider
            .userInteractionEnabled = NO;

        if (didShowActivityIndicator &&
            browserProviderInterface.mainBrowserProvider.browser) {
          id<BrowserCoordinatorCommands> handler =
              HandlerForProtocol(browserProviderInterface.mainBrowserProvider
                                     .browser->GetCommandDispatcher(),
                                 BrowserCoordinatorCommands);
          [handler hideActivityOverlay];
        }
      }
      browserProviderInterface.mainBrowserProvider.userInteractionEnabled = YES;
      browserProviderInterface.incognitoBrowserProvider.userInteractionEnabled =
          YES;
      [browserProviderInterface.currentBrowserProvider setPrimary:YES];
    }
    // `completionBlock` is run once, not once per scene.
    if (completionBlock)
      completionBlock();
  };

  BrowsingDataRemoverFactory::GetForBrowserState(browserState)
      ->Remove(timePeriod, removeMask, base::BindOnce(removalCompletion));
}

#if BUILDFLAG(IOS_CREDENTIAL_PROVIDER_ENABLED)
- (void)performFaviconsCleanup {
  ChromeBrowserState* browserState = self.currentBrowserState;
  if (!browserState)
    return;

  syncer::SyncService* syncService =
      SyncServiceFactory::GetForBrowserState(browserState);
  // Only use the fallback to the Google server when fetching favicons for
  // normal encryption synced users because they are the only users who
  // consented to share data to Google. The other types of synced users did not.
  BOOL isPasswordSyncEnabled =
      password_manager_util::IsPasswordSyncNormalEncryptionEnabled(syncService);
  if (isPasswordSyncEnabled) {
    UpdateFaviconsStorage(
        IOSChromeFaviconLoaderFactory::GetForBrowserState(browserState),
        /*sync_enabled=*/isPasswordSyncEnabled);
  }
}
#endif

#pragma mark - BlockingSceneCommands

- (void)activateBlockingScene:(UIScene*)requestingScene {
  id<UIBlockerTarget> uiBlocker = self.appState.currentUIBlocker;
  if (!uiBlocker) {
    return;
  }

  [uiBlocker bringBlockerToFront:requestingScene];
}

@end

#pragma mark - TestingOnly

@implementation MainController (TestingOnly)

- (void)setStartupParametersWithURL:(const GURL&)launchURL {
  NSString* sourceApplication = @"Fake App";
  SceneState* sceneState = self.appState.foregroundActiveScene;
  sceneState.controller.startupParameters = [ChromeAppStartupParameters
      newChromeAppStartupParametersWithURL:net::NSURLWithGURL(launchURL)
                     fromSourceApplication:sourceApplication];
}

@end