chromium/ios/chrome/browser/ui/settings/password/password_settings/password_settings_coordinator.mm

// Copyright 2022 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/browser/ui/settings/password/password_settings/password_settings_coordinator.h"

#import <UIKit/UIKit.h>

#import "base/debug/dump_without_crashing.h"
#import "base/i18n/message_formatter.h"
#import "base/metrics/user_metrics.h"
#import "base/strings/sys_string_conversions.h"
#import "components/google/core/common/google_util.h"
#import "components/keyed_service/core/service_access_type.h"
#import "components/password_manager/core/browser/ui/saved_passwords_presenter.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/affiliations/model/ios_chrome_affiliation_service_factory.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_account_password_store_factory.h"
#import "ios/chrome/browser/passwords/model/ios_chrome_profile_password_store_factory.h"
#import "ios/chrome/browser/passwords/model/metrics/ios_password_manager_metrics.h"
#import "ios/chrome/browser/passwords/model/metrics/ios_password_manager_visits_recorder.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/public/commands/application_commands.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/commands/open_new_tab_command.h"
#import "ios/chrome/browser/shared/public/commands/snackbar_commands.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/ui/settings/elements/enterprise_info_popover_view_controller.h"
#import "ios/chrome/browser/ui/settings/password/password_settings/password_bulk_move_handler.h"
#import "ios/chrome/browser/ui/settings/password/password_settings/password_export_handler.h"
#import "ios/chrome/browser/ui/settings/password/password_settings/password_settings_constants.h"
#import "ios/chrome/browser/ui/settings/password/password_settings/password_settings_coordinator_delegate.h"
#import "ios/chrome/browser/ui/settings/password/password_settings/password_settings_mediator.h"
#import "ios/chrome/browser/ui/settings/password/password_settings/password_settings_view_controller.h"
#import "ios/chrome/browser/ui/settings/password/password_settings/scoped_password_settings_reauth_module_override.h"
#import "ios/chrome/browser/ui/settings/password/passwords_in_other_apps/passwords_in_other_apps_coordinator.h"
#import "ios/chrome/browser/ui/settings/password/reauthentication/reauthentication_coordinator.h"
#import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
#import "ios/chrome/browser/ui/settings/utils/password_utils.h"
#import "ios/chrome/browser/webauthn/model/ios_passkey_model_factory.h"
#import "ios/chrome/common/ui/reauthentication/reauthentication_module.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "net/base/apple/url_conversions.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/l10n/l10n_util_mac.h"

namespace {

// The user action for when the bulk move passwords to account confirmation
// dialog's cancel button is clicked.
constexpr const char* kBulkMovePasswordsToAccountConfirmationDialogCancelled =
    "Mobile.PasswordsSettings.BulkSavePasswordsToAccountDialog.Cancelled";

// The user action for when the bulk move passwords to account confirmation
// dialog's accept button is clicked.
constexpr const char* kBulkMovePasswordsToAccountConfirmationDialogAccepted =
    "Mobile.PasswordsSettings.BulkSavePasswordsToAccountDialog.Accepted";

}  // namespace

// Methods to update state in response to actions taken in the Export
// ActivityViewController.
@protocol ExportActivityViewControllerDelegate <NSObject>

// Used to reset the export state when the activity view disappears.
- (void)resetExport;

@end

// Convenience wrapper around ActivityViewController for presenting share sheet
// at the end of the export flow. We do not use completionWithItemsHandler
// because it fails sometimes; see crbug.com/820053.
@interface ExportActivityViewController : UIActivityViewController

- (instancetype)initWithActivityItems:(NSArray*)activityItems
                             delegate:(id<ExportActivityViewControllerDelegate>)
                                          delegate;

@end

@implementation ExportActivityViewController {
  __weak id<ExportActivityViewControllerDelegate> _weakDelegate;
}

- (instancetype)initWithActivityItems:(NSArray*)activityItems
                             delegate:(id<ExportActivityViewControllerDelegate>)
                                          delegate {
  self = [super initWithActivityItems:activityItems applicationActivities:nil];
  if (self) {
    _weakDelegate = delegate;
  }

  return self;
}

- (void)viewDidDisappear:(BOOL)animated {
  [_weakDelegate resetExport];
  [super viewDidDisappear:animated];
}

@end

@interface PasswordSettingsCoordinator () <
    ExportActivityViewControllerDelegate,
    BulkMoveLocalPasswordsToAccountHandler,
    PasswordExportHandler,
    PasswordSettingsPresentationDelegate,
    PasswordsInOtherAppsCoordinatorDelegate,
    PopoverLabelViewControllerDelegate,
    ReauthenticationCoordinatorDelegate,
    SettingsNavigationControllerDelegate>

@end

@implementation PasswordSettingsCoordinator {
  // Main view controller for this coordinator.
  PasswordSettingsViewController* _passwordSettingsViewController;

  // The presented SettingsNavigationController containing
  // `passwordSettingsViewController`.
  SettingsNavigationController* _settingsNavigationController;

  // The coupled mediator.
  PasswordSettingsMediator* _mediator;

  // Command dispatcher.
  __weak id<ApplicationCommands> _dispatcher;

  // Module handling reauthentication before accessing sensitive data.
  ReauthenticationModule* _reauthModule;

  // Coordinator for the "Passwords in Other Apps" screen.
  PasswordsInOtherAppsCoordinator* _passwordsInOtherAppsCoordinator;

  // Coordinator for blocking Password Settings until Local Authentication is
  // passed. Used for requiring authentication when opening Password Settings
  // from outside the Password Manager and when the app is
  // backgrounded/foregrounded with Password Settings opened.
  ReauthenticationCoordinator* _reauthCoordinator;

  // Service which gives us a view on users' saved passwords.
  std::unique_ptr<password_manager::SavedPasswordsPresenter>
      _savedPasswordsPresenter;

  // Alert informing the user that passwords are being prepared for
  // export.
  UIAlertController* _preparingPasswordsAlert;

  // For recording visits after successful authentication.
  IOSPasswordManagerVisitsRecorder* _visitsRecorder;
}

#pragma mark - ChromeCoordinator

- (void)start {
  ChromeBrowserState* browserState = self.browser->GetBrowserState();

  _reauthModule = password_manager::BuildReauthenticationModule();

  _savedPasswordsPresenter =
      std::make_unique<password_manager::SavedPasswordsPresenter>(
          IOSChromeAffiliationServiceFactory::GetForBrowserState(browserState),
          IOSChromeProfilePasswordStoreFactory::GetForBrowserState(
              browserState, ServiceAccessType::EXPLICIT_ACCESS),
          IOSChromeAccountPasswordStoreFactory::GetForBrowserState(
              browserState, ServiceAccessType::EXPLICIT_ACCESS),
          IOSPasskeyModelFactory::GetForBrowserState(browserState));

  _mediator = [[PasswordSettingsMediator alloc]
         initWithReauthenticationModule:_reauthModule
                savedPasswordsPresenter:_savedPasswordsPresenter.get()
      bulkMovePasswordsToAccountHandler:self
                          exportHandler:self
                            prefService:browserState->GetPrefs()
                        identityManager:IdentityManagerFactory::
                                            GetForBrowserState(browserState)
                            syncService:SyncServiceFactory::GetForBrowserState(
                                            browserState)];

  _dispatcher = static_cast<id<ApplicationCommands>>(
      self.browser->GetCommandDispatcher());

  _passwordSettingsViewController =
      [[PasswordSettingsViewController alloc] init];

  _passwordSettingsViewController.presentationDelegate = self;

  _settingsNavigationController = [[SettingsNavigationController alloc]
      initWithRootViewController:_passwordSettingsViewController
                         browser:self.browser
                        delegate:self];

  _mediator.consumer = _passwordSettingsViewController;
  _passwordSettingsViewController.delegate = _mediator;

  _visitsRecorder = [[IOSPasswordManagerVisitsRecorder alloc]
      initWithPasswordManagerSurface:password_manager::PasswordManagerSurface::
                                         kPasswordSettings];

  // Only record visit if no auth is required, otherwise wait for successful
  // auth.
  if (_skipAuthenticationOnStart) {
    [_visitsRecorder maybeRecordVisitMetric];
  }

  [self startReauthCoordinatorWithAuthOnStart:!_skipAuthenticationOnStart];

  [self.baseViewController presentViewController:_settingsNavigationController
                                        animated:YES
                                      completion:nil];
}

- (void)stop {
  [self stopWithUIDismissal:YES];
}

#pragma mark - PasswordSettingsCoordinator

- (void)stopWithUIDismissal:(BOOL)shouldDismissUI {
  if (shouldDismissUI) {
    [_settingsNavigationController.presentingViewController
        dismissViewControllerAnimated:NO
                           completion:nil];
  }

  [_passwordsInOtherAppsCoordinator stop];
  _passwordsInOtherAppsCoordinator.delegate = nil;
  _passwordsInOtherAppsCoordinator = nil;

  _passwordSettingsViewController.presentationDelegate = nil;
  _passwordSettingsViewController.delegate = nil;
  _passwordSettingsViewController = nil;
  [_settingsNavigationController cleanUpSettings];
  _settingsNavigationController = nil;
  _preparingPasswordsAlert = nil;

  _dispatcher = nil;
  _reauthModule = nil;

  [_mediator disconnect];
  _mediator.consumer = nil;
  _mediator = nil;
  _savedPasswordsPresenter.reset();

  [self stopReauthenticationCoordinator];
}

#pragma mark - PasswordSettingsPresentationDelegate

- (void)startExportFlow {
  UIAlertController* exportConfirmation = [UIAlertController
      alertControllerWithTitle:nil
                       message:l10n_util::GetNSString(
                                   IDS_IOS_EXPORT_PASSWORDS_ALERT_MESSAGE)
                preferredStyle:UIAlertControllerStyleActionSheet];
  exportConfirmation.view.accessibilityIdentifier =
      kPasswordSettingsExportConfirmViewId;

  UIAlertAction* cancelAction =
      [UIAlertAction actionWithTitle:l10n_util::GetNSString(
                                         IDS_IOS_EXPORT_PASSWORDS_CANCEL_BUTTON)
                               style:UIAlertActionStyleCancel
                             handler:^(UIAlertAction* action){
                             }];
  [exportConfirmation addAction:cancelAction];

  __weak __typeof(self) weakSelf = self;
  UIAlertAction* exportAction = [UIAlertAction
      actionWithTitle:l10n_util::GetNSString(IDS_IOS_EXPORT_PASSWORDS)
                style:UIAlertActionStyleDefault
              handler:^(UIAlertAction* action) {
                [weakSelf onStartExportFlowConfirmed];
              }];

  [exportConfirmation addAction:exportAction];

  exportConfirmation.popoverPresentationController.sourceView =
      [_passwordSettingsViewController sourceViewForAlerts];
  exportConfirmation.popoverPresentationController.sourceRect =
      [_passwordSettingsViewController sourceRectForPasswordExportAlerts];

  [_passwordSettingsViewController presentViewController:exportConfirmation
                                                animated:YES
                                              completion:nil];
}

- (void)showManagedPrefInfoForSourceView:(UIButton*)sourceView {
  // EnterpriseInfoPopoverViewController automatically handles reenabling the
  // `sourceView`, so we don't need to add any dismiss handlers or delegation,
  // just present the bubble.
  EnterpriseInfoPopoverViewController* bubbleViewController =
      [[EnterpriseInfoPopoverViewController alloc] initWithEnterpriseName:nil];
  bubbleViewController.delegate = self;

  // Set the anchor and arrow direction of the bubble.
  bubbleViewController.popoverPresentationController.sourceView = sourceView;
  bubbleViewController.popoverPresentationController.sourceRect =
      sourceView.bounds;
  bubbleViewController.popoverPresentationController.permittedArrowDirections =
      UIPopoverArrowDirectionAny;

  [_passwordSettingsViewController presentViewController:bubbleViewController
                                                animated:YES
                                              completion:nil];
}

- (void)showPasswordsInOtherAppsScreen {
  DCHECK(!_passwordsInOtherAppsCoordinator);
  [self stopReauthCoordinatorBeforeStartingChildCoordinator];
  _passwordsInOtherAppsCoordinator = [[PasswordsInOtherAppsCoordinator alloc]
      initWithBaseNavigationController:_settingsNavigationController
                               browser:self.browser];
  _passwordsInOtherAppsCoordinator.delegate = self;
  [_passwordsInOtherAppsCoordinator start];
}

- (void)showOnDeviceEncryptionSetUp {
  GURL URL = google_util::AppendGoogleLocaleParam(
      GURL(kOnDeviceEncryptionOptInURL),
      GetApplicationContext()->GetApplicationLocale());
  OpenNewTabCommand* command = [OpenNewTabCommand commandWithURLFromChrome:URL];
  [_dispatcher closeSettingsUIAndOpenURL:command];
}

- (void)showOnDeviceEncryptionHelp {
  GURL URL = GURL(kOnDeviceEncryptionLearnMoreURL);
  OpenNewTabCommand* command = [OpenNewTabCommand commandWithURLFromChrome:URL];
  [_dispatcher closeSettingsUIAndOpenURL:command];
}

#pragma mark - PopoverLabelViewControllerDelegate

- (void)didTapLinkURL:(NSURL*)URL {
  [_dispatcher
      openURLInNewTab:[OpenNewTabCommand
                          commandWithURLFromChrome:net::GURLWithNSURL(URL)
                                       inIncognito:NO]];
}

#pragma mark - BulkMoveLocalPasswordsToAccountHandler

- (void)showAuthenticationForMovePasswordsToAccountWithMessage:
    (NSString*)message {
  [_mediator userDidStartBulkMoveLocalPasswordsToAccountFlow];
}

- (void)showConfirmationDialogWithAlertTitle:(NSString*)alertTitle
                            alertDescription:(NSString*)alertDescription {
  // Create the confirmation alert.
  UIAlertController* movePasswordsConfirmation = [UIAlertController
      alertControllerWithTitle:alertTitle
                       message:alertDescription
                preferredStyle:UIAlertControllerStyleActionSheet];
  movePasswordsConfirmation.view.accessibilityIdentifier =
      kPasswordSettingsBulkMovePasswordsToAccountAlertViewId;

  // Create the cancel action.
  UIAlertAction* cancelAction = [UIAlertAction
      actionWithTitle:
          l10n_util::GetNSString(
              IDS_IOS_PASSWORD_SETTINGS_BULK_UPLOAD_PASSWORDS_ALERT_CANCEL)
                style:UIAlertActionStyleCancel
              handler:^(UIAlertAction* action) {
                base::RecordAction(base::UserMetricsAction(
                    kBulkMovePasswordsToAccountConfirmationDialogCancelled));
              }];
  [movePasswordsConfirmation addAction:cancelAction];

  // Create the accept action (i.e. move passwords to account).
  __weak __typeof(self) weakSelf = self;
  UIAlertAction* movePasswordsAction = [UIAlertAction
      actionWithTitle:
          l10n_util::GetNSString(
              IDS_IOS_PASSWORD_SETTINGS_BULK_UPLOAD_PASSWORDS_ALERT_BUTTON)
                style:UIAlertActionStyleDefault
              handler:^(UIAlertAction* action) {
                base::RecordAction(base::UserMetricsAction(
                    kBulkMovePasswordsToAccountConfirmationDialogAccepted));
                [weakSelf
                    showAuthenticationForMovePasswordsToAccountWithMessage:
                        alertTitle];
              }];

  [movePasswordsConfirmation addAction:movePasswordsAction];

  movePasswordsConfirmation.popoverPresentationController.sourceView =
      [_passwordSettingsViewController sourceViewForAlerts];
  movePasswordsConfirmation.popoverPresentationController.sourceRect =
      [_passwordSettingsViewController sourceRectForBulkMovePasswordsToAccount];

  // Show the alert.
  [_passwordSettingsViewController
      presentViewController:movePasswordsConfirmation
                   animated:YES
                 completion:nil];
}

- (void)showMovedToAccountSnackbarWithPasswordCount:(int)count
                                          userEmail:(std::string)email {
  std::u16string pattern = l10n_util::GetStringUTF16(
      IDS_IOS_PASSWORD_SETTINGS_BULK_UPLOAD_PASSWORDS_SNACKBAR_MESSAGE);
  std::u16string result = base::i18n::MessageFormatter::FormatWithNamedArgs(
      pattern, "COUNT", count, "EMAIL", base::UTF8ToUTF16(email));

  TriggerHapticFeedbackForNotification(UINotificationFeedbackTypeSuccess);
  id<SnackbarCommands> handler = HandlerForProtocol(
      self.browser->GetCommandDispatcher(), SnackbarCommands);
  [handler showSnackbarWithMessage:base::SysUTF16ToNSString(result)
                        buttonText:nil
                     messageAction:nil
                  completionAction:nil];
}

#pragma mark - PasswordExportHandler

- (void)showActivityViewWithActivityItems:(NSArray*)activityItems
                        completionHandler:(void (^)(NSString* activityType,
                                                    BOOL completed,
                                                    NSArray* returnedItems,
                                                    NSError* activityError))
                                              completionHandler {
  ExportActivityViewController* activityViewController =
      [[ExportActivityViewController alloc] initWithActivityItems:activityItems
                                                         delegate:self];
  NSArray* excludedActivityTypes = @[
    UIActivityTypeAddToReadingList, UIActivityTypeAirDrop,
    UIActivityTypeCopyToPasteboard, UIActivityTypeOpenInIBooks,
    UIActivityTypePostToFacebook, UIActivityTypePostToFlickr,
    UIActivityTypePostToTencentWeibo, UIActivityTypePostToTwitter,
    UIActivityTypePostToVimeo, UIActivityTypePostToWeibo, UIActivityTypePrint
  ];
  [activityViewController setExcludedActivityTypes:excludedActivityTypes];

  [activityViewController setCompletionWithItemsHandler:completionHandler];

  UIView* sourceView = [_passwordSettingsViewController sourceViewForAlerts];
  CGRect sourceRect =
      [_passwordSettingsViewController sourceRectForPasswordExportAlerts];

  activityViewController.modalPresentationStyle = UIModalPresentationPopover;
  activityViewController.popoverPresentationController.sourceView = sourceView;
  activityViewController.popoverPresentationController.sourceRect = sourceRect;
  activityViewController.popoverPresentationController
      .permittedArrowDirections =
      UIPopoverArrowDirectionUp | UIPopoverArrowDirectionDown;

  [self presentViewControllerForExportFlow:activityViewController];
}

- (void)showExportErrorAlertWithLocalizedReason:(NSString*)localizedReason {
  UIAlertController* alertController = [UIAlertController
      alertControllerWithTitle:l10n_util::GetNSString(
                                   IDS_IOS_EXPORT_PASSWORDS_FAILED_ALERT_TITLE)
                       message:localizedReason
                preferredStyle:UIAlertControllerStyleAlert];
  UIAlertAction* okAction =
      [UIAlertAction actionWithTitle:l10n_util::GetNSString(IDS_OK)
                               style:UIAlertActionStyleDefault
                             handler:nil];
  [alertController addAction:okAction];
  [self presentViewControllerForExportFlow:alertController];
}

- (void)showPreparingPasswordsAlert {
  _preparingPasswordsAlert = [UIAlertController
      alertControllerWithTitle:
          l10n_util::GetNSString(IDS_IOS_EXPORT_PASSWORDS_PREPARING_ALERT_TITLE)
                       message:nil
                preferredStyle:UIAlertControllerStyleAlert];
  __weak __typeof(self) weakSelf = self;
  UIAlertAction* cancelAction =
      [UIAlertAction actionWithTitle:l10n_util::GetNSString(
                                         IDS_IOS_EXPORT_PASSWORDS_CANCEL_BUTTON)
                               style:UIAlertActionStyleCancel
                             handler:^(UIAlertAction*) {
                               [weakSelf onExportFlowCancelled];
                             }];
  [_preparingPasswordsAlert addAction:cancelAction];
  [_passwordSettingsViewController
      presentViewController:_preparingPasswordsAlert
                   animated:YES
                 completion:nil];
}

- (void)showSetPasscodeForPasswordExportDialog {
  [self showSetPasscodeDialogWithContent:
            l10n_util::GetNSString(
                IDS_IOS_SETTINGS_EXPORT_PASSWORDS_SET_UP_SCREENLOCK_CONTENT)];
}

#pragma mark - ExportActivityViewControllerDelegate

- (void)resetExport {
  [_mediator userDidCompleteExportFlow];
}

#pragma mark - PasswordsInOtherAppsCoordinatorDelegate

- (void)passwordsInOtherAppsCoordinatorDidRemove:
    (PasswordsInOtherAppsCoordinator*)coordinator {
  DCHECK_EQ(_passwordsInOtherAppsCoordinator, coordinator);
  [_passwordsInOtherAppsCoordinator stop];
  _passwordsInOtherAppsCoordinator.delegate = nil;
  _passwordsInOtherAppsCoordinator = nil;
  [self restartReauthCoordinator];
}

#pragma mark - PasswordManagerReauthenticationDelegate

- (void)dismissPasswordManagerAfterFailedReauthentication {
  [_delegate dismissPasswordManagerAfterFailedReauthentication];
}

#pragma mark - SettingsNavigationControllerDelegate

- (void)closeSettings {
  // Dismiss UI and notify parent coordinator.
  __weak __typeof(self) weakSelf = self;
  [self.baseViewController dismissViewControllerAnimated:YES
                                              completion:^{
                                                [weakSelf settingsWasDismissed];
                                              }];
}

- (void)settingsWasDismissed {
  [self.delegate passwordSettingsCoordinatorDidRemove:self];
}

#pragma mark - ReauthenticationCoordinatorDelegate

- (void)successfulReauthenticationWithCoordinator:
    (ReauthenticationCoordinator*)coordinator {
  [_visitsRecorder maybeRecordVisitMetric];
}

- (void)dismissUIAfterFailedReauthenticationWithCoordinator:
    (ReauthenticationCoordinator*)coordinator {
  CHECK_EQ(_reauthCoordinator, coordinator);
  [_delegate dismissPasswordManagerAfterFailedReauthentication];
}

- (void)willPushReauthenticationViewController {
  // Cancel password export flow before authentication UI is presented.
  if (_preparingPasswordsAlert.beingPresented) {
    [_preparingPasswordsAlert dismissViewControllerAnimated:NO completion:nil];
    [_mediator exportFlowCanceled];
    _preparingPasswordsAlert = nil;
  }
}

#pragma mark - Private

// Closes the settings and load the passcode help article in a new tab.
- (void)showPasscodeHelp {
  GURL URL = GURL(kPasscodeArticleURL);
  OpenNewTabCommand* command = [OpenNewTabCommand commandWithURLFromChrome:URL];
  [_dispatcher closeSettingsUIAndOpenURL:command];
}

// Helper to show the "set passcode" dialog with customizable content.
- (void)showSetPasscodeDialogWithContent:(NSString*)content {
  UIAlertController* alertController = [UIAlertController
      alertControllerWithTitle:l10n_util::GetNSString(
                                   IDS_IOS_SETTINGS_SET_UP_SCREENLOCK_TITLE)
                       message:content
                preferredStyle:UIAlertControllerStyleAlert];

  __weak __typeof(self) weakSelf = self;
  UIAlertAction* learnAction = [UIAlertAction
      actionWithTitle:l10n_util::GetNSString(
                          IDS_IOS_SETTINGS_SET_UP_SCREENLOCK_LEARN_HOW)
                style:UIAlertActionStyleDefault
              handler:^(UIAlertAction*) {
                [weakSelf showPasscodeHelp];
              }];
  [alertController addAction:learnAction];
  UIAlertAction* okAction =
      [UIAlertAction actionWithTitle:l10n_util::GetNSString(IDS_OK)
                               style:UIAlertActionStyleDefault
                             handler:nil];
  [alertController addAction:okAction];
  alertController.preferredAction = okAction;
  [_passwordSettingsViewController presentViewController:alertController
                                                animated:YES
                                              completion:nil];
}

// Helper method for presenting several ViewControllers used in the export flow.
// Ensures that the "Preparing passwords" alert is dismissed when something is
// ready to replace it.
- (void)presentViewControllerForExportFlow:(UIViewController*)viewController {
  if (_preparingPasswordsAlert.beingPresented) {
    __weak __typeof(self) weakSelf = self;
    [_preparingPasswordsAlert
        dismissViewControllerAnimated:YES
                           completion:^{
                             [weakSelf presentViewControllerForExportFlow:
                                           viewController];
                           }];
  } else {
    [_passwordSettingsViewController presentViewController:viewController
                                                  animated:YES
                                                completion:nil];
  }
}

// Starts reauthCoordinator.
// - authOnStart: Pass `YES` to cover Password Settings with an empty view
// controller until successful Local Authentication when reauthCoordinator
// starts.
//
// Local authentication is required every time the current
// scene is backgrounded and foregrounded until reauthCoordinator is stopped.
- (void)startReauthCoordinatorWithAuthOnStart:(BOOL)authOnStart {
  if (_reauthCoordinator) {
    // The previous reauth coordinator should have been stopped and deallocated
    // by now. Create a crash report without crashing and gracefully handle the
    // error by cleaning up the old coordinator.
    base::debug::DumpWithoutCrashing();
    [_reauthCoordinator stopAndPopViewController];
  }

  _reauthCoordinator = [[ReauthenticationCoordinator alloc]
      initWithBaseNavigationController:_settingsNavigationController
                               browser:self.browser
                reauthenticationModule:_reauthModule
                           authOnStart:authOnStart];

  _reauthCoordinator.delegate = self;

  [_reauthCoordinator start];
}

// Stops reauthCoordinator.
- (void)stopReauthenticationCoordinator {
  [_reauthCoordinator stop];
  _reauthCoordinator.delegate = nil;
  _reauthCoordinator = nil;
}

// Stop reauth coordinator when a child coordinator will be started.
//
// Needed so reauth coordinator doesn't block for reauth if the scene state
// changes while the child coordinator is presenting its content. The child
// coordinator will add its own reauth coordinator to block its content for
// reauth.
- (void)stopReauthCoordinatorBeforeStartingChildCoordinator {
  // See PasswordsCoordinator
  // stopReauthCoordinatorBeforeStartingChildCoordinator.
  [_reauthCoordinator stopAndPopViewController];
  _reauthCoordinator.delegate = nil;
  _reauthCoordinator = nil;
}

// Starts reauthCoordinator after a child coordinator content was dismissed.
- (void)restartReauthCoordinator {
  // Restart reauth coordinator so it monitors scene state changes and requests
  // local authentication after the scene goes to the background.
  [self startReauthCoordinatorWithAuthOnStart:NO];
}

// Starts the export passwords flow after the user confirmed the corresponding
// alert.
- (void)onStartExportFlowConfirmed {
  [_mediator userDidStartExportFlow];
}

// Cancels the password export flow.
- (void)onExportFlowCancelled {
  [_mediator exportFlowCanceled];
}

@end