1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_coordinator.h"
6
7#include <vector>
8
9#include "base/ios/ios_util.h"
10#include "base/mac/foundation_util.h"
11#include "base/strings/utf_string_conversions.h"
12#include "components/autofill/core/browser/personal_data_manager.h"
13#include "components/autofill/core/common/autofill_features.h"
14#import "components/autofill/ios/browser/js_suggestion_manager.h"
15#include "components/keyed_service/core/service_access_type.h"
16#include "components/password_manager/core/browser/password_ui_utils.h"
17#include "components/strings/grit/components_strings.h"
18#include "ios/chrome/browser/autofill/personal_data_manager_factory.h"
19#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
20#import "ios/chrome/browser/main/browser.h"
21#include "ios/chrome/browser/passwords/ios_chrome_password_store_factory.h"
22#import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
23#import "ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_mediator.h"
24#import "ios/chrome/browser/ui/autofill/form_input_accessory/form_input_accessory_view_controller.h"
25#import "ios/chrome/browser/ui/autofill/manual_fill/address_coordinator.h"
26#import "ios/chrome/browser/ui/autofill/manual_fill/card_coordinator.h"
27#import "ios/chrome/browser/ui/autofill/manual_fill/fallback_view_controller.h"
28#import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_accessory_view_controller.h"
29#import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_all_password_coordinator.h"
30#import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_injection_handler.h"
31#import "ios/chrome/browser/ui/autofill/manual_fill/manual_fill_password_coordinator.h"
32#import "ios/chrome/browser/ui/commands/application_commands.h"
33#import "ios/chrome/browser/ui/commands/command_dispatcher.h"
34#import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
35#import "ios/chrome/browser/ui/commands/security_alert_commands.h"
36#import "ios/chrome/browser/ui/main/scene_state.h"
37#import "ios/chrome/browser/ui/main/scene_state_browser_agent.h"
38#include "ios/chrome/browser/ui/util/ui_util.h"
39#import "ios/chrome/browser/web_state_list/web_state_list.h"
40#import "ios/chrome/common/ui/reauthentication/reauthentication_module.h"
41#include "ios/chrome/grit/ios_strings.h"
42#import "ios/web/public/web_state.h"
43#include "ui/base/l10n/l10n_util_mac.h"
44
45#if !defined(__has_feature) || !__has_feature(objc_arc)
46#error "This file requires ARC support."
47#endif
48
49@interface FormInputAccessoryCoordinator () <
50    AddressCoordinatorDelegate,
51    CardCoordinatorDelegate,
52    FormInputAccessoryMediatorDelegate,
53    ManualFillAccessoryViewControllerDelegate,
54    PasswordCoordinatorDelegate,
55    SecurityAlertCommands>
56
57// Coordinator in charge of the presenting password autofill options as a modal.
58@property(nonatomic, strong)
59    ManualFillAllPasswordCoordinator* allPasswordCoordinator;
60
61// The Mediator for the input accessory view controller.
62@property(nonatomic, strong)
63    FormInputAccessoryMediator* formInputAccessoryMediator;
64
65// The View Controller for the input accessory view.
66@property(nonatomic, strong)
67    FormInputAccessoryViewController* formInputAccessoryViewController;
68
69// The object in charge of interacting with the web view. Used to fill the data
70// in the forms.
71@property(nonatomic, strong) ManualFillInjectionHandler* injectionHandler;
72
73// Reauthentication Module used for re-authentication.
74@property(nonatomic, strong) ReauthenticationModule* reauthenticationModule;
75
76// Modal alert.
77@property(nonatomic, strong) AlertCoordinator* alertCoordinator;
78
79@end
80
81@implementation FormInputAccessoryCoordinator
82
83- (instancetype)initWithBaseViewController:(UIViewController*)viewController
84                                   browser:(Browser*)browser {
85  self = [super initWithBaseViewController:viewController browser:browser];
86  if (self) {
87    CommandDispatcher* dispatcher = browser->GetCommandDispatcher();
88    [dispatcher startDispatchingToTarget:self
89                             forProtocol:@protocol(SecurityAlertCommands)];
90    __weak id<SecurityAlertCommands> securityAlertHandler =
91        HandlerForProtocol(dispatcher, SecurityAlertCommands);
92    _reauthenticationModule = [[ReauthenticationModule alloc] init];
93    _injectionHandler = [[ManualFillInjectionHandler alloc]
94          initWithWebStateList:browser->GetWebStateList()
95          securityAlertHandler:securityAlertHandler
96        reauthenticationModule:_reauthenticationModule];
97  }
98  return self;
99}
100
101- (void)start {
102  self.formInputAccessoryViewController =
103      [[FormInputAccessoryViewController alloc]
104          initWithManualFillAccessoryViewControllerDelegate:self];
105
106  auto passwordStore = IOSChromePasswordStoreFactory::GetForBrowserState(
107      self.browser->GetBrowserState(), ServiceAccessType::EXPLICIT_ACCESS);
108
109  // There is no personal data manager in OTR (incognito). Get the original
110  // one for manual fallback.
111  autofill::PersonalDataManager* personalDataManager =
112      autofill::PersonalDataManagerFactory::GetForBrowserState(
113          self.browser->GetBrowserState()->GetOriginalChromeBrowserState());
114
115  AppState* appState = SceneStateBrowserAgent::FromBrowser(self.browser)
116                           ->GetSceneState()
117                           .appState;
118  __weak id<SecurityAlertCommands> securityAlertHandler = HandlerForProtocol(
119      self.browser->GetCommandDispatcher(), SecurityAlertCommands);
120  self.formInputAccessoryMediator = [[FormInputAccessoryMediator alloc]
121            initWithConsumer:self.formInputAccessoryViewController
122                    delegate:self
123                webStateList:self.browser->GetWebStateList()
124         personalDataManager:personalDataManager
125               passwordStore:passwordStore
126                    appState:appState
127        securityAlertHandler:securityAlertHandler
128      reauthenticationModule:self.reauthenticationModule];
129  self.formInputAccessoryViewController.formSuggestionClient =
130      self.formInputAccessoryMediator;
131}
132
133- (void)stop {
134  [self stopChildren];
135
136  [self.formInputAccessoryViewController restoreOriginalKeyboardView];
137  self.formInputAccessoryViewController = nil;
138
139  [self.formInputAccessoryMediator disconnect];
140  self.formInputAccessoryMediator = nil;
141
142  [self.allPasswordCoordinator stop];
143  self.allPasswordCoordinator = nil;
144}
145
146- (void)reset {
147  [self stopChildren];
148  [self.formInputAccessoryMediator enableSuggestions];
149  [self.formInputAccessoryViewController reset];
150}
151
152#pragma mark - Presenting Children
153
154- (void)stopChildren {
155  for (ChromeCoordinator* coordinator in self.childCoordinators) {
156    [coordinator stop];
157  }
158  [self.childCoordinators removeAllObjects];
159}
160
161- (void)startPasswordsFromButton:(UIButton*)button {
162  WebStateList* webStateList = self.browser->GetWebStateList();
163  DCHECK(webStateList->GetActiveWebState());
164  const GURL& URL = webStateList->GetActiveWebState()->GetLastCommittedURL();
165  ManualFillPasswordCoordinator* passwordCoordinator =
166      [[ManualFillPasswordCoordinator alloc]
167          initWithBaseViewController:self.baseViewController
168                             browser:self.browser
169                                 URL:URL
170                    injectionHandler:self.injectionHandler];
171  passwordCoordinator.delegate = self;
172  if (IsIPadIdiom()) {
173    [passwordCoordinator presentFromButton:button];
174  } else {
175    [self.formInputAccessoryViewController
176        presentView:passwordCoordinator.viewController.view];
177  }
178
179  [self.childCoordinators addObject:passwordCoordinator];
180}
181
182- (void)startCardsFromButton:(UIButton*)button {
183  CardCoordinator* cardCoordinator = [[CardCoordinator alloc]
184      initWithBaseViewController:self.baseViewController
185                         browser:self.browser
186                injectionHandler:self.injectionHandler];
187  cardCoordinator.delegate = self;
188  if (IsIPadIdiom()) {
189    [cardCoordinator presentFromButton:button];
190  } else {
191    [self.formInputAccessoryViewController
192        presentView:cardCoordinator.viewController.view];
193  }
194
195  [self.childCoordinators addObject:cardCoordinator];
196}
197
198- (void)startAddressFromButton:(UIButton*)button {
199  AddressCoordinator* addressCoordinator = [[AddressCoordinator alloc]
200      initWithBaseViewController:self.baseViewController
201                         browser:self.browser
202                injectionHandler:self.injectionHandler];
203  addressCoordinator.delegate = self;
204  if (IsIPadIdiom()) {
205    [addressCoordinator presentFromButton:button];
206  } else {
207    [self.formInputAccessoryViewController
208        presentView:addressCoordinator.viewController.view];
209  }
210
211  [self.childCoordinators addObject:addressCoordinator];
212}
213
214#pragma mark - FormInputAccessoryMediatorDelegate
215
216- (void)mediatorDidDetectKeyboardHide:(FormInputAccessoryMediator*)mediator {
217  // On iOS 13, beta 3, the popover is not dismissed when the keyboard hides.
218  // This explicitly dismiss any popover.
219  // TODO(crbug.com/1116037): Verify if this workaround is still needed.
220  if (base::ios::IsRunningOnIOS13OrLater() && IsIPadIdiom()) {
221    [self reset];
222  }
223}
224
225- (void)mediatorDidDetectMovingToBackground:
226    (FormInputAccessoryMediator*)mediator {
227  [self reset];
228}
229
230#pragma mark - ManualFillAccessoryViewControllerDelegate
231
232- (void)keyboardButtonPressed {
233  [self reset];
234}
235
236- (void)accountButtonPressed:(UIButton*)sender {
237  [self stopChildren];
238  [self startAddressFromButton:sender];
239  [self.formInputAccessoryViewController lockManualFallbackView];
240  [self.formInputAccessoryMediator disableSuggestions];
241}
242
243- (void)cardButtonPressed:(UIButton*)sender {
244  [self stopChildren];
245  [self startCardsFromButton:sender];
246  [self.formInputAccessoryViewController lockManualFallbackView];
247  [self.formInputAccessoryMediator disableSuggestions];
248}
249
250- (void)passwordButtonPressed:(UIButton*)sender {
251  [self stopChildren];
252  [self startPasswordsFromButton:sender];
253  [self.formInputAccessoryViewController lockManualFallbackView];
254  [self.formInputAccessoryMediator disableSuggestions];
255}
256
257#pragma mark - FallbackCoordinatorDelegate
258
259- (void)fallbackCoordinatorDidDismissPopover:
260    (FallbackCoordinator*)fallbackCoordinator {
261  [self reset];
262}
263
264#pragma mark - PasswordCoordinatorDelegate
265
266- (void)openPasswordSettings {
267  [self reset];
268  [self.navigator openPasswordSettings];
269}
270
271- (void)openAllPasswordsPicker {
272  [self showConfirmationDialogToUseOtherPassword];
273}
274
275#pragma mark - CardCoordinatorDelegate
276
277- (void)openCardSettings {
278  [self reset];
279  [self.navigator openCreditCardSettings];
280}
281
282#pragma mark - AddressCoordinatorDelegate
283
284- (void)openAddressSettings {
285  [self reset];
286  [self.navigator openAddressSettings];
287}
288
289#pragma mark - SecurityAlertCommands
290
291- (void)presentSecurityWarningAlertWithText:(NSString*)body {
292  NSString* alertTitle =
293      l10n_util::GetNSString(IDS_IOS_MANUAL_FALLBACK_NOT_SECURE_TITLE);
294  NSString* defaultActionTitle =
295      l10n_util::GetNSString(IDS_IOS_MANUAL_FALLBACK_NOT_SECURE_OK_BUTTON);
296
297  UIAlertController* alert =
298      [UIAlertController alertControllerWithTitle:alertTitle
299                                          message:body
300                                   preferredStyle:UIAlertControllerStyleAlert];
301  UIAlertAction* defaultAction =
302      [UIAlertAction actionWithTitle:defaultActionTitle
303                               style:UIAlertActionStyleDefault
304                             handler:^(UIAlertAction* action){
305                             }];
306  [alert addAction:defaultAction];
307  UIViewController* presenter = self.baseViewController;
308  while (presenter.presentedViewController) {
309    presenter = presenter.presentedViewController;
310  }
311  [presenter presentViewController:alert animated:YES completion:nil];
312}
313
314- (void)showSetPasscodeDialog {
315  UIAlertController* alertController = [UIAlertController
316      alertControllerWithTitle:l10n_util::GetNSString(
317                                   IDS_IOS_SETTINGS_SET_UP_SCREENLOCK_TITLE)
318                       message:l10n_util::GetNSString(
319                                   IDS_IOS_AUTOFILL_SET_UP_SCREENLOCK_CONTENT)
320                preferredStyle:UIAlertControllerStyleAlert];
321
322  __weak id<ApplicationCommands> applicationCommandsHandler =
323      HandlerForProtocol(self.browser->GetCommandDispatcher(),
324                         ApplicationCommands);
325  OpenNewTabCommand* command =
326      [OpenNewTabCommand commandWithURLFromChrome:GURL(kPasscodeArticleURL)];
327
328  UIAlertAction* learnAction = [UIAlertAction
329      actionWithTitle:l10n_util::GetNSString(
330                          IDS_IOS_SETTINGS_SET_UP_SCREENLOCK_LEARN_HOW)
331                style:UIAlertActionStyleDefault
332              handler:^(UIAlertAction*) {
333                [applicationCommandsHandler openURLInNewTab:command];
334              }];
335  [alertController addAction:learnAction];
336  UIAlertAction* okAction =
337      [UIAlertAction actionWithTitle:l10n_util::GetNSString(IDS_OK)
338                               style:UIAlertActionStyleDefault
339                             handler:nil];
340  [alertController addAction:okAction];
341  alertController.preferredAction = okAction;
342
343  [self.baseViewController presentViewController:alertController
344                                        animated:YES
345                                      completion:nil];
346}
347
348#pragma mark - Private
349
350// Shows confirmation dialog before opening Other passwords.
351- (void)showConfirmationDialogToUseOtherPassword {
352  WebStateList* webStateList = self.browser->GetWebStateList();
353  const GURL& URL = webStateList->GetActiveWebState()->GetLastCommittedURL();
354  base::string16 origin = base::ASCIIToUTF16(
355      password_manager::GetShownOrigin(url::Origin::Create(URL)));
356  NSString* title =
357      l10n_util::GetNSString(IDS_IOS_CONFIRM_USING_OTHER_PASSWORD_TITLE);
358  NSString* message = l10n_util::GetNSStringF(
359      IDS_IOS_CONFIRM_USING_OTHER_PASSWORD_DESCRIPTION, origin);
360
361  self.alertCoordinator = [[AlertCoordinator alloc]
362      initWithBaseViewController:self.baseViewController
363                         browser:self.browser
364                           title:title
365                         message:message];
366
367  __weak __typeof__(self) weakSelf = self;
368
369  [self.alertCoordinator addItemWithTitle:l10n_util::GetNSString(IDS_CANCEL)
370                                   action:nil
371                                    style:UIAlertActionStyleCancel];
372
373  NSString* actionTitle =
374      l10n_util::GetNSString(IDS_IOS_CONFIRM_USING_OTHER_PASSWORD_CONTINUE);
375  [self.alertCoordinator addItemWithTitle:actionTitle
376                                   action:^{
377                                     [weakSelf showAllPasswords];
378                                   }
379                                    style:UIAlertActionStyleDefault];
380
381  [self.alertCoordinator start];
382}
383
384// Opens other passwords.
385- (void)showAllPasswords {
386  [self reset];
387  self.allPasswordCoordinator = [[ManualFillAllPasswordCoordinator alloc]
388      initWithBaseViewController:self.baseViewController
389                         browser:self.browser
390                injectionHandler:self.injectionHandler];
391  [self.allPasswordCoordinator start];
392}
393
394@end
395