blob: 6a7e81cd41cbd96c805e56311feb58c63828fd3e [file] [log] [blame]
// Copyright 2014 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/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.h"
#include <vector>
#include "base/ios/ios_util.h"
#include "base/mac/foundation_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/personal_data_manager.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/keyed_service/core/service_access_type.h"
#include "components/password_manager/core/browser/manage_passwords_referrer.h"
#include "components/password_manager/core/browser/password_ui_utils.h"
#include "components/strings/grit/components_strings.h"
#include "ios/chrome/browser/autofill/personal_data_manager_factory.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#import "ios/chrome/browser/main/browser.h"
#include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h"
#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
#import "ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.h"
#import "ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_view_controller.h"
#import "ios/chrome/browser/ui/autofill/manual_fill/address_coordinator.h"
#import "ios/chrome/browser/ui/autofill/manual_fill/card_coordinator.h"
#import "ios/chrome/browser/ui/autofill/manual_fill/fallback_view_controller.h"
#import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_accessory_view_controller.h"
#import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_all_password_coordinator.h"
#import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_injection_handler.h"
#import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_coordinator.h"
#import "ios/chrome/browser/ui/commands/application_commands.h"
#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
#import "ios/chrome/browser/ui/commands/security_alert_commands.h"
#import "ios/chrome/browser/ui/main/scene_state.h"
#import "ios/chrome/browser/ui/main/scene_state_browser_agent.h"
#include "ios/chrome/browser/ui/util/ui_util.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/common/ui/reauthentication/reauthentication_module.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/web/public/web_state.h"
#include "ui/base/l10n/l10n_util_mac.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface FormInputAccessoryCoordinator () <
AddressCoordinatorDelegate,
CardCoordinatorDelegate,
FormInputAccessoryMediatorHandler,
ManualFillAccessoryViewControllerDelegate,
PasswordCoordinatorDelegate,
SecurityAlertCommands>
// Coordinator in charge of the presenting password autofill options as a modal.
@property(nonatomic, strong)
ManualFillAllPasswordCoordinator* allPasswordCoordinator;
// The Mediator for the input accessory view controller.
@property(nonatomic, strong)
FormInputAccessoryMediator* formInputAccessoryMediator;
// The View Controller for the input accessory view.
@property(nonatomic, strong)
FormInputAccessoryViewController* formInputAccessoryViewController;
// The object in charge of interacting with the web view. Used to fill the data
// in the forms.
@property(nonatomic, strong) ManualFillInjectionHandler* injectionHandler;
// Reauthentication Module used for re-authentication.
@property(nonatomic, strong) ReauthenticationModule* reauthenticationModule;
// Modal alert.
@property(nonatomic, strong) AlertCoordinator* alertCoordinator;
@end
@implementation FormInputAccessoryCoordinator
- (instancetype)initWithBaseViewController:(UIViewController*)viewController
browser:(Browser*)browser {
self = [super initWithBaseViewController:viewController browser:browser];
if (self) {
CommandDispatcher* dispatcher = browser->GetCommandDispatcher();
[dispatcher startDispatchingToTarget:self
forProtocol:@protocol(SecurityAlertCommands)];
__weak id<SecurityAlertCommands> securityAlertHandler =
HandlerForProtocol(dispatcher, SecurityAlertCommands);
_reauthenticationModule = [[ReauthenticationModule alloc] init];
_injectionHandler = [[ManualFillInjectionHandler alloc]
initWithWebStateList:browser->GetWebStateList()
securityAlertHandler:securityAlertHandler
reauthenticationModule:_reauthenticationModule];
}
return self;
}
- (void)start {
self.formInputAccessoryViewController =
[[FormInputAccessoryViewController alloc]
initWithManualFillAccessoryViewControllerDelegate:self];
auto passwordStore = IOSChromePasswordStoreFactory::GetForBrowserState(
self.browser->GetBrowserState(), ServiceAccessType::EXPLICIT_ACCESS);
// There is no personal data manager in OTR (incognito). Get the original
// one for manual fallback.
autofill::PersonalDataManager* personalDataManager =
autofill::PersonalDataManagerFactory::GetForBrowserState(
self.browser->GetBrowserState()->GetOriginalChromeBrowserState());
AppState* appState = SceneStateBrowserAgent::FromBrowser(self.browser)
->GetSceneState()
.appState;
__weak id<SecurityAlertCommands> securityAlertHandler = HandlerForProtocol(
self.browser->GetCommandDispatcher(), SecurityAlertCommands);
self.formInputAccessoryMediator = [[FormInputAccessoryMediator alloc]
initWithConsumer:self.formInputAccessoryViewController
handler:self
webStateList:self.browser->GetWebStateList()
personalDataManager:personalDataManager
passwordStore:passwordStore
appState:appState
securityAlertHandler:securityAlertHandler
reauthenticationModule:self.reauthenticationModule];
self.formInputAccessoryViewController.formSuggestionClient =
self.formInputAccessoryMediator;
}
- (void)stop {
[self stopChildren];
[self.formInputAccessoryViewController restoreOriginalKeyboardView];
self.formInputAccessoryViewController = nil;
[self.formInputAccessoryMediator disconnect];
self.formInputAccessoryMediator = nil;
[self.allPasswordCoordinator stop];
self.allPasswordCoordinator = nil;
}
- (void)reset {
[self stopChildren];
[self.formInputAccessoryMediator enableSuggestions];
[self.formInputAccessoryViewController reset];
}
#pragma mark - Presenting Children
- (void)stopChildren {
for (ChromeCoordinator* coordinator in self.childCoordinators) {
[coordinator stop];
}
[self.childCoordinators removeAllObjects];
}
- (void)startPasswordsFromButton:(UIButton*)button
invokedOnPasswordField:(BOOL)invokedOnPasswordField {
WebStateList* webStateList = self.browser->GetWebStateList();
DCHECK(webStateList->GetActiveWebState());
const GURL& URL = webStateList->GetActiveWebState()->GetLastCommittedURL();
ManualFillPasswordCoordinator* passwordCoordinator =
[[ManualFillPasswordCoordinator alloc]
initWithBaseViewController:self.baseViewController
browser:self.browser
URL:URL
injectionHandler:self.injectionHandler
invokedOnPasswordField:invokedOnPasswordField];
passwordCoordinator.delegate = self;
if (IsIPadIdiom()) {
[passwordCoordinator presentFromButton:button];
} else {
[self.formInputAccessoryViewController
presentView:passwordCoordinator.viewController.view];
}
[self.childCoordinators addObject:passwordCoordinator];
}
- (void)startCardsFromButton:(UIButton*)button {
CardCoordinator* cardCoordinator = [[CardCoordinator alloc]
initWithBaseViewController:self.baseViewController
browser:self.browser
injectionHandler:self.injectionHandler];
cardCoordinator.delegate = self;
if (IsIPadIdiom()) {
[cardCoordinator presentFromButton:button];
} else {
[self.formInputAccessoryViewController
presentView:cardCoordinator.viewController.view];
}
[self.childCoordinators addObject:cardCoordinator];
}
- (void)startAddressFromButton:(UIButton*)button {
AddressCoordinator* addressCoordinator = [[AddressCoordinator alloc]
initWithBaseViewController:self.baseViewController
browser:self.browser
injectionHandler:self.injectionHandler];
addressCoordinator.delegate = self;
if (IsIPadIdiom()) {
[addressCoordinator presentFromButton:button];
} else {
[self.formInputAccessoryViewController
presentView:addressCoordinator.viewController.view];
}
[self.childCoordinators addObject:addressCoordinator];
}
#pragma mark - FormInputAccessoryMediatorHandler
- (void)mediatorDidDetectKeyboardHide:(FormInputAccessoryMediator*)mediator {
// On iOS 13, beta 3, the popover is not dismissed when the keyboard hides.
// This explicitly dismiss any popover.
// TODO(crbug.com/1116037): Verify if this workaround is still needed.
if (base::ios::IsRunningOnIOS13OrLater() && IsIPadIdiom()) {
[self reset];
}
}
- (void)mediatorDidDetectMovingToBackground:
(FormInputAccessoryMediator*)mediator {
[self reset];
}
#pragma mark - ManualFillAccessoryViewControllerDelegate
- (void)keyboardButtonPressed {
[self reset];
}
- (void)accountButtonPressed:(UIButton*)sender {
[self stopChildren];
[self startAddressFromButton:sender];
[self.formInputAccessoryViewController lockManualFallbackView];
[self.formInputAccessoryMediator disableSuggestions];
}
- (void)cardButtonPressed:(UIButton*)sender {
[self stopChildren];
[self startCardsFromButton:sender];
[self.formInputAccessoryViewController lockManualFallbackView];
[self.formInputAccessoryMediator disableSuggestions];
}
- (void)passwordButtonPressed:(UIButton*)sender {
[self stopChildren];
BOOL invokedOnPasswordField =
[self.formInputAccessoryMediator lastFocusedFieldWasPassword];
[self startPasswordsFromButton:sender
invokedOnPasswordField:invokedOnPasswordField];
[self.formInputAccessoryViewController lockManualFallbackView];
[self.formInputAccessoryMediator disableSuggestions];
}
#pragma mark - FallbackCoordinatorDelegate
- (void)fallbackCoordinatorDidDismissPopover:
(FallbackCoordinator*)fallbackCoordinator {
[self reset];
}
#pragma mark - PasswordCoordinatorDelegate
- (void)openPasswordSettings {
[self reset];
[self.navigator openPasswordSettings];
UMA_HISTOGRAM_ENUMERATION(
"PasswordManager.ManagePasswordsReferrer",
password_manager::ManagePasswordsReferrer::kPasswordsAccessorySheet);
}
- (void)openAllPasswordsPicker {
[self showConfirmationDialogToUseOtherPassword];
}
#pragma mark - CardCoordinatorDelegate
- (void)openCardSettings {
[self reset];
[self.navigator openCreditCardSettings];
}
#pragma mark - AddressCoordinatorDelegate
- (void)openAddressSettings {
[self reset];
[self.navigator openAddressSettings];
}
#pragma mark - SecurityAlertCommands
- (void)presentSecurityWarningAlertWithText:(NSString*)body {
NSString* alertTitle =
l10n_util::GetNSString(IDS_IOS_MANUAL_FALLBACK_NOT_SECURE_TITLE);
NSString* defaultActionTitle =
l10n_util::GetNSString(IDS_IOS_MANUAL_FALLBACK_NOT_SECURE_OK_BUTTON);
UIAlertController* alert =
[UIAlertController alertControllerWithTitle:alertTitle
message:body
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction =
[UIAlertAction actionWithTitle:defaultActionTitle
style:UIAlertActionStyleDefault
handler:^(UIAlertAction* action){
}];
[alert addAction:defaultAction];
UIViewController* presenter = self.baseViewController;
while (presenter.presentedViewController) {
presenter = presenter.presentedViewController;
}
[presenter presentViewController:alert animated:YES completion:nil];
}
- (void)showSetPasscodeDialog {
UIAlertController* alertController = [UIAlertController
alertControllerWithTitle:l10n_util::GetNSString(
IDS_IOS_SETTINGS_SET_UP_SCREENLOCK_TITLE)
message:l10n_util::GetNSString(
IDS_IOS_AUTOFILL_SET_UP_SCREENLOCK_CONTENT)
preferredStyle:UIAlertControllerStyleAlert];
__weak id<ApplicationCommands> applicationCommandsHandler =
HandlerForProtocol(self.browser->GetCommandDispatcher(),
ApplicationCommands);
OpenNewTabCommand* command =
[OpenNewTabCommand commandWithURLFromChrome:GURL(kPasscodeArticleURL)];
UIAlertAction* learnAction = [UIAlertAction
actionWithTitle:l10n_util::GetNSString(
IDS_IOS_SETTINGS_SET_UP_SCREENLOCK_LEARN_HOW)
style:UIAlertActionStyleDefault
handler:^(UIAlertAction*) {
[applicationCommandsHandler openURLInNewTab:command];
}];
[alertController addAction:learnAction];
UIAlertAction* okAction =
[UIAlertAction actionWithTitle:l10n_util::GetNSString(IDS_OK)
style:UIAlertActionStyleDefault
handler:nil];
[alertController addAction:okAction];
alertController.preferredAction = okAction;
[self.baseViewController presentViewController:alertController
animated:YES
completion:nil];
}
#pragma mark - Private
// Shows confirmation dialog before opening Other passwords.
- (void)showConfirmationDialogToUseOtherPassword {
WebStateList* webStateList = self.browser->GetWebStateList();
const GURL& URL = webStateList->GetActiveWebState()->GetLastCommittedURL();
std::u16string origin = base::ASCIIToUTF16(
password_manager::GetShownOrigin(url::Origin::Create(URL)));
NSString* title =
l10n_util::GetNSString(IDS_IOS_CONFIRM_USING_OTHER_PASSWORD_TITLE);
NSString* message = l10n_util::GetNSStringF(
IDS_IOS_CONFIRM_USING_OTHER_PASSWORD_DESCRIPTION, origin);
self.alertCoordinator = [[AlertCoordinator alloc]
initWithBaseViewController:self.baseViewController
browser:self.browser
title:title
message:message];
__weak __typeof__(self) weakSelf = self;
[self.alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
action:nil
style:UIAlertActionStyleCancel];
NSString* actionTitle =
l10n_util::GetNSString(IDS_IOS_CONFIRM_USING_OTHER_PASSWORD_CONTINUE);
[self.alertCoordinator addItemWithTitle:actionTitle
action:^{
[weakSelf showAllPasswords];
}
style:UIAlertActionStyleDefault];
[self.alertCoordinator start];
}
// Opens other passwords.
- (void)showAllPasswords {
[self reset];
self.allPasswordCoordinator = [[ManualFillAllPasswordCoordinator alloc]
initWithBaseViewController:self.baseViewController
browser:self.browser
injectionHandler:self.injectionHandler];
[self.allPasswordCoordinator start];
}
@end