autofill_dialog_controller_impl.cc revision a93a17c8d99d686bd4a1511e5504e5e6cc9fcadf
1// Copyright 2013 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#include "chrome/browser/ui/autofill/autofill_dialog_controller_impl.h" 6 7#include <algorithm> 8#include <string> 9 10#include "base/bind.h" 11#include "base/logging.h" 12#include "base/prefs/pref_service.h" 13#include "base/string_util.h" 14#include "base/strings/string_number_conversions.h" 15#include "base/strings/string_split.h" 16#include "base/time.h" 17#include "base/utf_string_conversions.h" 18#include "chrome/browser/autofill/personal_data_manager_factory.h" 19#include "chrome/browser/browser_process.h" 20#include "chrome/browser/extensions/shell_window_registry.h" 21#include "chrome/browser/profiles/profile.h" 22#include "chrome/browser/ui/autofill/autofill_dialog_view.h" 23#include "chrome/browser/ui/autofill/data_model_wrapper.h" 24#include "chrome/browser/ui/base_window.h" 25#include "chrome/browser/ui/browser.h" 26#include "chrome/browser/ui/browser_finder.h" 27#include "chrome/browser/ui/browser_navigator.h" 28#include "chrome/browser/ui/browser_window.h" 29#include "chrome/browser/ui/extensions/native_app_window.h" 30#include "chrome/browser/ui/extensions/shell_window.h" 31#include "chrome/common/chrome_version_info.h" 32#include "chrome/common/pref_names.h" 33#include "chrome/common/url_constants.h" 34#include "components/autofill/browser/autofill_data_model.h" 35#include "components/autofill/browser/autofill_manager.h" 36#include "components/autofill/browser/autofill_type.h" 37#include "components/autofill/browser/personal_data_manager.h" 38#include "components/autofill/browser/risk/fingerprint.h" 39#include "components/autofill/browser/risk/proto/fingerprint.pb.h" 40#include "components/autofill/browser/validation.h" 41#include "components/autofill/browser/wallet/cart.h" 42#include "components/autofill/browser/wallet/full_wallet.h" 43#include "components/autofill/browser/wallet/instrument.h" 44#include "components/autofill/browser/wallet/wallet_address.h" 45#include "components/autofill/browser/wallet/wallet_items.h" 46#include "components/autofill/browser/wallet/wallet_service_url.h" 47#include "components/autofill/browser/wallet/wallet_signin_helper.h" 48#include "components/autofill/common/form_data.h" 49#include "components/user_prefs/pref_registry_syncable.h" 50#include "content/public/browser/navigation_controller.h" 51#include "content/public/browser/navigation_details.h" 52#include "content/public/browser/navigation_entry.h" 53#include "content/public/browser/notification_service.h" 54#include "content/public/browser/notification_types.h" 55#include "content/public/browser/web_contents.h" 56#include "content/public/browser/web_contents_view.h" 57#include "content/public/common/url_constants.h" 58#include "grit/chromium_strings.h" 59#include "grit/component_resources.h" 60#include "grit/generated_resources.h" 61#include "grit/theme_resources.h" 62#include "grit/webkit_resources.h" 63#include "net/cert/cert_status_flags.h" 64#include "ui/base/l10n/l10n_util.h" 65#include "ui/base/resource/resource_bundle.h" 66#include "ui/gfx/canvas.h" 67#include "ui/gfx/color_utils.h" 68#include "ui/gfx/skbitmap_operations.h" 69 70namespace autofill { 71 72namespace { 73 74const bool kPayWithoutWalletDefault = false; 75 76// This is a pseudo-scientifically chosen maximum amount we want a fronting 77// (proxy) card to be able to charge. The current actual max is $2000. Using 78// only $1850 leaves some room for tax and shipping, etc. TODO(dbeam): send a 79// special value to the server to just ask for the maximum so we don't need to 80// hardcode it here (http://crbug.com/180731). TODO(dbeam): also maybe allow 81// users to give us this number via an <input> (http://crbug.com/180733). 82const int kCartMax = 1850; 83const char kCartCurrency[] = "USD"; 84 85const char kAddNewItemKey[] = "add-new-item"; 86const char kManageItemsKey[] = "manage-items"; 87const char kSameAsBillingKey[] = "same-as-billing"; 88 89// Returns true if |input| should be shown when |field_type| has been requested. 90bool InputTypeMatchesFieldType(const DetailInput& input, 91 AutofillFieldType field_type) { 92 // If any credit card expiration info is asked for, show both month and year 93 // inputs. 94 if (field_type == CREDIT_CARD_EXP_4_DIGIT_YEAR || 95 field_type == CREDIT_CARD_EXP_2_DIGIT_YEAR || 96 field_type == CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR || 97 field_type == CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR || 98 field_type == CREDIT_CARD_EXP_MONTH) { 99 return input.type == CREDIT_CARD_EXP_4_DIGIT_YEAR || 100 input.type == CREDIT_CARD_EXP_MONTH; 101 } 102 103 if (field_type == CREDIT_CARD_TYPE) 104 return input.type == CREDIT_CARD_NUMBER; 105 106 return input.type == field_type; 107} 108 109// Returns true if |input| should be used for a site-requested |field|. 110bool DetailInputMatchesField(const DetailInput& input, 111 const AutofillField& field) { 112 return InputTypeMatchesFieldType(input, field.type()); 113} 114 115bool IsCreditCardType(AutofillFieldType type) { 116 return AutofillType(type).group() == AutofillType::CREDIT_CARD; 117} 118 119// Returns true if |input| should be used to fill a site-requested |field| which 120// is notated with a "shipping" tag, for use when the user has decided to use 121// the billing address as the shipping address. 122bool DetailInputMatchesShippingField(const DetailInput& input, 123 const AutofillField& field) { 124 if (field.type() == NAME_FULL) 125 return input.type == CREDIT_CARD_NAME; 126 127 // Equivalent billing field type is used to support UseBillingAsShipping 128 // usecase. 129 AutofillFieldType field_type = 130 AutofillType::GetEquivalentBillingFieldType(field.type()); 131 132 return InputTypeMatchesFieldType(input, field_type); 133} 134 135// Constructs |inputs| from template data. 136void BuildInputs(const DetailInput* input_template, 137 size_t template_size, 138 DetailInputs* inputs) { 139 for (size_t i = 0; i < template_size; ++i) { 140 const DetailInput* input = &input_template[i]; 141 inputs->push_back(*input); 142 } 143} 144 145// Initializes |form_group| from user-entered data. 146void FillFormGroupFromOutputs(const DetailOutputMap& detail_outputs, 147 FormGroup* form_group) { 148 for (DetailOutputMap::const_iterator iter = detail_outputs.begin(); 149 iter != detail_outputs.end(); ++iter) { 150 if (!iter->second.empty()) { 151 AutofillFieldType type = iter->first->type; 152 if (type == ADDRESS_HOME_COUNTRY || type == ADDRESS_BILLING_COUNTRY) { 153 form_group->SetInfo(type, 154 iter->second, 155 g_browser_process->GetApplicationLocale()); 156 } else { 157 form_group->SetRawInfo(iter->first->type, iter->second); 158 } 159 } 160 } 161} 162 163// Get billing info from |output| and put it into |card|, |cvc|, and |profile|. 164// These outparams are required because |card|/|profile| accept different types 165// of raw info, and CreditCard doesn't save CVCs. 166void GetBillingInfoFromOutputs(const DetailOutputMap& output, 167 CreditCard* card, 168 string16* cvc, 169 AutofillProfile* profile) { 170 for (DetailOutputMap::const_iterator it = output.begin(); 171 it != output.end(); ++it) { 172 string16 trimmed; 173 TrimWhitespace(it->second, TRIM_ALL, &trimmed); 174 175 // Special case CVC as CreditCard just swallows it. 176 if (it->first->type == CREDIT_CARD_VERIFICATION_CODE) { 177 if (cvc) 178 cvc->assign(trimmed); 179 } else if (it->first->type == ADDRESS_HOME_COUNTRY || 180 it->first->type == ADDRESS_BILLING_COUNTRY) { 181 profile->SetInfo(it->first->type, 182 trimmed, 183 g_browser_process->GetApplicationLocale()); 184 } else { 185 // Copy the credit card name to |profile| in addition to |card| as 186 // wallet::Instrument requires a recipient name for its billing address. 187 if (profile && it->first->type == CREDIT_CARD_NAME) 188 profile->SetRawInfo(NAME_FULL, trimmed); 189 190 if (IsCreditCardType(it->first->type)) { 191 if (card) 192 card->SetRawInfo(it->first->type, trimmed); 193 } else if (profile) { 194 profile->SetRawInfo(it->first->type, trimmed); 195 } 196 } 197 } 198} 199 200// Returns the containing window for the given |web_contents|. The containing 201// window might be a browser window for a Chrome tab, or it might be a shell 202// window for a platform app. 203BaseWindow* GetBaseWindowForWebContents( 204 const content::WebContents* web_contents) { 205 Browser* browser = chrome::FindBrowserWithWebContents(web_contents); 206 if (browser) 207 return browser->window(); 208 209 gfx::NativeWindow native_window = 210 web_contents->GetView()->GetTopLevelNativeWindow(); 211 ShellWindow* shell_window = 212 extensions::ShellWindowRegistry:: 213 GetShellWindowForNativeWindowAnyProfile(native_window); 214 return shell_window->GetBaseWindow(); 215} 216 217// Extracts the string value of a field with |type| from |output|. This is 218// useful when you only need the value of 1 input from a section of view inputs. 219string16 GetValueForType(const DetailOutputMap& output, 220 AutofillFieldType type) { 221 for (DetailOutputMap::const_iterator it = output.begin(); 222 it != output.end(); ++it) { 223 if (it->first->type == type) 224 return it->second; 225 } 226 NOTREACHED(); 227 return string16(); 228} 229 230} // namespace 231 232AutofillDialogController::~AutofillDialogController() {} 233 234AutofillDialogControllerImpl::~AutofillDialogControllerImpl() { 235 if (popup_controller_) 236 popup_controller_->Hide(); 237 238 GetMetricLogger().LogDialogInitialUserState( 239 dialog_type_, initial_user_state_); 240} 241 242// static 243base::WeakPtr<AutofillDialogControllerImpl> 244 AutofillDialogControllerImpl::Create( 245 content::WebContents* contents, 246 const FormData& form_structure, 247 const GURL& source_url, 248 const DialogType dialog_type, 249 const base::Callback<void(const FormStructure*, 250 const std::string&)>& callback) { 251 // AutofillDialogControllerImpl owns itself. 252 AutofillDialogControllerImpl* autofill_dialog_controller = 253 new AutofillDialogControllerImpl(contents, 254 form_structure, 255 source_url, 256 dialog_type, 257 callback); 258 return autofill_dialog_controller->weak_ptr_factory_.GetWeakPtr(); 259} 260 261// static 262void AutofillDialogControllerImpl::RegisterUserPrefs( 263 user_prefs::PrefRegistrySyncable* registry) { 264 registry->RegisterBooleanPref( 265 ::prefs::kAutofillDialogPayWithoutWallet, 266 kPayWithoutWalletDefault, 267 user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); 268} 269 270void AutofillDialogControllerImpl::Show() { 271 dialog_shown_timestamp_ = base::Time::Now(); 272 273 content::NavigationEntry* entry = contents_->GetController().GetActiveEntry(); 274 const GURL& active_url = entry ? entry->GetURL() : contents_->GetURL(); 275 invoked_from_same_origin_ = active_url.GetOrigin() == source_url_.GetOrigin(); 276 277 // Log any relevant UI metrics and security exceptions. 278 GetMetricLogger().LogDialogUiEvent( 279 dialog_type_, AutofillMetrics::DIALOG_UI_SHOWN); 280 281 GetMetricLogger().LogDialogSecurityMetric( 282 dialog_type_, AutofillMetrics::SECURITY_METRIC_DIALOG_SHOWN); 283 284 if (RequestingCreditCardInfo() && !TransmissionWillBeSecure()) { 285 GetMetricLogger().LogDialogSecurityMetric( 286 dialog_type_, 287 AutofillMetrics::SECURITY_METRIC_CREDIT_CARD_OVER_HTTP); 288 } 289 290 if (!invoked_from_same_origin_) { 291 GetMetricLogger().LogDialogSecurityMetric( 292 dialog_type_, 293 AutofillMetrics::SECURITY_METRIC_CROSS_ORIGIN_FRAME); 294 } 295 296 // Determine what field types should be included in the dialog. 297 bool has_types = false; 298 bool has_sections = false; 299 form_structure_.ParseFieldTypesFromAutocompleteAttributes(&has_types, 300 &has_sections); 301 // Fail if the author didn't specify autocomplete types. 302 if (!has_types) { 303 callback_.Run(NULL, std::string()); 304 delete this; 305 return; 306 } 307 308 const DetailInput kEmailInputs[] = { 309 { 1, EMAIL_ADDRESS, IDS_AUTOFILL_DIALOG_PLACEHOLDER_EMAIL }, 310 }; 311 312 const DetailInput kCCInputs[] = { 313 { 2, CREDIT_CARD_NUMBER, IDS_AUTOFILL_DIALOG_PLACEHOLDER_CARD_NUMBER }, 314 { 3, CREDIT_CARD_EXP_MONTH }, 315 { 3, CREDIT_CARD_EXP_4_DIGIT_YEAR }, 316 { 3, CREDIT_CARD_VERIFICATION_CODE, IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC }, 317 { 4, CREDIT_CARD_NAME, IDS_AUTOFILL_DIALOG_PLACEHOLDER_CARDHOLDER_NAME }, 318 }; 319 320 const DetailInput kBillingInputs[] = { 321 { 5, ADDRESS_BILLING_LINE1, 322 IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESS_LINE_1 }, 323 { 6, ADDRESS_BILLING_LINE2, 324 IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESS_LINE_2 }, 325 { 7, ADDRESS_BILLING_CITY, 326 IDS_AUTOFILL_DIALOG_PLACEHOLDER_LOCALITY }, 327 // TODO(estade): state placeholder should depend on locale. 328 { 8, ADDRESS_BILLING_STATE, IDS_AUTOFILL_FIELD_LABEL_STATE }, 329 { 8, ADDRESS_BILLING_ZIP, 330 IDS_AUTOFILL_DIALOG_PLACEHOLDER_POSTAL_CODE, 0.5 }, 331 // TODO(estade): this should have a default based on the locale. 332 { 9, ADDRESS_BILLING_COUNTRY, 0 }, 333 // TODO(ramankk): Add billing specific phone number. 334 { 10, PHONE_HOME_WHOLE_NUMBER, 335 IDS_AUTOFILL_DIALOG_PLACEHOLDER_PHONE_NUMBER }, 336 }; 337 338 const DetailInput kShippingInputs[] = { 339 { 11, NAME_FULL, IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESSEE_NAME }, 340 { 12, ADDRESS_HOME_LINE1, IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESS_LINE_1 }, 341 { 13, ADDRESS_HOME_LINE2, IDS_AUTOFILL_DIALOG_PLACEHOLDER_ADDRESS_LINE_2 }, 342 { 14, ADDRESS_HOME_CITY, IDS_AUTOFILL_DIALOG_PLACEHOLDER_LOCALITY }, 343 { 15, ADDRESS_HOME_STATE, IDS_AUTOFILL_FIELD_LABEL_STATE }, 344 { 15, ADDRESS_HOME_ZIP, IDS_AUTOFILL_DIALOG_PLACEHOLDER_POSTAL_CODE, 0.5 }, 345 { 16, ADDRESS_HOME_COUNTRY, 0 }, 346 { 17, PHONE_HOME_WHOLE_NUMBER, 347 IDS_AUTOFILL_DIALOG_PLACEHOLDER_PHONE_NUMBER }, 348 }; 349 350 BuildInputs(kEmailInputs, 351 arraysize(kEmailInputs), 352 &requested_email_fields_); 353 354 BuildInputs(kCCInputs, 355 arraysize(kCCInputs), 356 &requested_cc_fields_); 357 358 BuildInputs(kBillingInputs, 359 arraysize(kBillingInputs), 360 &requested_billing_fields_); 361 362 BuildInputs(kCCInputs, 363 arraysize(kCCInputs), 364 &requested_cc_billing_fields_); 365 BuildInputs(kBillingInputs, 366 arraysize(kBillingInputs), 367 &requested_cc_billing_fields_); 368 369 BuildInputs(kShippingInputs, 370 arraysize(kShippingInputs), 371 &requested_shipping_fields_); 372 373 SuggestionsUpdated(); 374 375 // TODO(estade): don't show the dialog if the site didn't specify the right 376 // fields. First we must figure out what the "right" fields are. 377 view_.reset(CreateView()); 378 view_->Show(); 379 GetManager()->AddObserver(this); 380 381 // Try to see if the user is already signed-in. 382 // If signed-in, fetch the user's Wallet data. 383 // Otherwise, see if the user could be signed in passively. 384 // TODO(aruslan): UMA metrics for sign-in. 385 if (account_chooser_model_.WalletIsSelected()) 386 GetWalletItems(); 387 else 388 LogDialogLatencyToShow(); 389} 390 391void AutofillDialogControllerImpl::Hide() { 392 if (view_) 393 view_->Hide(); 394} 395 396void AutofillDialogControllerImpl::UpdateProgressBar(double value) { 397 view_->UpdateProgressBar(value); 398} 399 400bool AutofillDialogControllerImpl::AutocheckoutIsRunning() const { 401 return !autocheckout_started_timestamp_.is_null(); 402} 403 404bool AutofillDialogControllerImpl::HadAutocheckoutError() const { 405 return had_autocheckout_error_; 406} 407 408void AutofillDialogControllerImpl::OnAutocheckoutError() { 409 had_autocheckout_error_ = true; 410 autocheckout_started_timestamp_ = base::Time(); 411 view_->UpdateNotificationArea(); 412 view_->UpdateButtonStrip(); 413 view_->UpdateDetailArea(); 414} 415 416//////////////////////////////////////////////////////////////////////////////// 417// AutofillDialogController implementation. 418 419string16 AutofillDialogControllerImpl::DialogTitle() const { 420 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_TITLE); 421} 422 423string16 AutofillDialogControllerImpl::EditSuggestionText() const { 424 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_EDIT); 425} 426 427string16 AutofillDialogControllerImpl::CancelButtonText() const { 428 return l10n_util::GetStringUTF16(IDS_CANCEL); 429} 430 431string16 AutofillDialogControllerImpl::ConfirmButtonText() const { 432 return l10n_util::GetStringUTF16(IsSubmitPausedOn(wallet::VERIFY_CVV) ? 433 IDS_AUTOFILL_DIALOG_VERIFY_BUTTON : IDS_AUTOFILL_DIALOG_SUBMIT_BUTTON); 434} 435 436string16 AutofillDialogControllerImpl::SaveLocallyText() const { 437 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SAVE_LOCALLY_CHECKBOX); 438} 439 440string16 AutofillDialogControllerImpl::ProgressBarText() const { 441 return l10n_util::GetStringUTF16( 442 IDS_AUTOFILL_DIALOG_AUTOCHECKOUT_PROGRESS_BAR); 443} 444 445string16 AutofillDialogControllerImpl::LegalDocumentsText() { 446 if (!IsPayingWithWallet()) 447 return string16(); 448 449 EnsureLegalDocumentsText(); 450 return legal_documents_text_; 451} 452 453DialogSignedInState AutofillDialogControllerImpl::SignedInState() const { 454 if (account_chooser_model_.had_wallet_error()) 455 return SIGN_IN_DISABLED; 456 457 if (signin_helper_ || !wallet_items_) 458 return REQUIRES_RESPONSE; 459 460 if (wallet_items_->HasRequiredAction(wallet::GAIA_AUTH)) 461 return REQUIRES_SIGN_IN; 462 463 if (wallet_items_->HasRequiredAction(wallet::PASSIVE_GAIA_AUTH)) 464 return REQUIRES_PASSIVE_SIGN_IN; 465 466 return SIGNED_IN; 467} 468 469bool AutofillDialogControllerImpl::ShouldShowSpinner() const { 470 return account_chooser_model_.WalletIsSelected() && 471 SignedInState() == REQUIRES_RESPONSE; 472} 473 474string16 AutofillDialogControllerImpl::AccountChooserText() const { 475 // TODO(aruslan): this should be l10n "Not using Google Wallet". 476 if (!account_chooser_model_.WalletIsSelected()) 477 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PAY_WITHOUT_WALLET); 478 479 if (SignedInState() == SIGNED_IN) 480 return account_chooser_model_.active_wallet_account_name(); 481 482 // In this case, the account chooser should be showing the signin link. 483 return string16(); 484} 485 486string16 AutofillDialogControllerImpl::SignInLinkText() const { 487 return l10n_util::GetStringUTF16( 488 signin_registrar_.IsEmpty() ? IDS_AUTOFILL_DIALOG_SIGN_IN : 489 IDS_AUTOFILL_DIALOG_PAY_WITHOUT_WALLET); 490} 491 492bool AutofillDialogControllerImpl::ShouldOfferToSaveInChrome() const { 493 // If Autocheckout is running, hide this checkbox so the progress bar has some 494 // room. If Autocheckout had an error, neither the [X] Save details in chrome 495 // nor the progress bar should show. 496 return !IsPayingWithWallet() && 497 !profile_->IsOffTheRecord() && 498 IsManuallyEditingAnySection() && 499 !ShouldShowProgressBar() && 500 !HadAutocheckoutError(); 501} 502 503bool AutofillDialogControllerImpl::IsDialogButtonEnabled( 504 ui::DialogButton button) const { 505 if (button == ui::DIALOG_BUTTON_OK) { 506 if (IsSubmitPausedOn(wallet::VERIFY_CVV)) 507 return true; 508 if (is_submitting_ || ShouldShowSpinner()) 509 return false; 510 return true; 511 } 512 513 DCHECK_EQ(ui::DIALOG_BUTTON_CANCEL, button); 514 // TODO(ahutter): Make it possible for the user to cancel out of the dialog 515 // while Autocheckout is in progress. 516 return had_autocheckout_error_ || !callback_.is_null(); 517} 518 519const std::vector<ui::Range>& AutofillDialogControllerImpl:: 520 LegalDocumentLinks() { 521 EnsureLegalDocumentsText(); 522 return legal_document_link_ranges_; 523} 524 525bool AutofillDialogControllerImpl::SectionIsActive(DialogSection section) 526 const { 527 if (IsSubmitPausedOn(wallet::VERIFY_CVV)) 528 return section == SECTION_CC_BILLING; 529 530 if (IsPayingWithWallet()) 531 return section == SECTION_CC_BILLING || section == SECTION_SHIPPING; 532 533 return section != SECTION_CC_BILLING; 534} 535 536bool AutofillDialogControllerImpl::HasCompleteWallet() const { 537 return wallet_items_.get() != NULL && 538 !wallet_items_->instruments().empty() && 539 !wallet_items_->addresses().empty(); 540} 541 542bool AutofillDialogControllerImpl::IsSubmitPausedOn( 543 wallet::RequiredAction required_action) const { 544 return full_wallet_ && full_wallet_->HasRequiredAction(required_action); 545} 546 547void AutofillDialogControllerImpl::GetWalletItems() { 548 GetWalletClient()->GetWalletItems(source_url_); 549} 550 551void AutofillDialogControllerImpl::HideSignIn() { 552 signin_registrar_.RemoveAll(); 553 view_->HideSignIn(); 554 view_->UpdateAccountChooser(); 555} 556 557void AutofillDialogControllerImpl::SignedInStateUpdated() { 558 if (!account_chooser_model_.WalletIsSelected()) 559 return; 560 561 switch (SignedInState()) { 562 case SIGNED_IN: 563 // Start fetching the user name if we don't know it yet. 564 if (account_chooser_model_.active_wallet_account_name().empty()) { 565 signin_helper_.reset(new wallet::WalletSigninHelper( 566 this, profile_->GetRequestContext())); 567 signin_helper_->StartUserNameFetch(); 568 } else { 569 LogDialogLatencyToShow(); 570 } 571 break; 572 573 case REQUIRES_SIGN_IN: 574 case SIGN_IN_DISABLED: 575 // Switch to the local account and refresh the dialog. 576 OnWalletSigninError(); 577 break; 578 579 case REQUIRES_PASSIVE_SIGN_IN: 580 // Attempt to passively sign in the user. 581 DCHECK(!signin_helper_); 582 account_chooser_model_.ClearActiveWalletAccountName(); 583 signin_helper_.reset(new wallet::WalletSigninHelper( 584 this, 585 profile_->GetRequestContext())); 586 signin_helper_->StartPassiveSignin(); 587 break; 588 589 case REQUIRES_RESPONSE: 590 break; 591 } 592} 593 594void AutofillDialogControllerImpl::OnWalletOrSigninUpdate() { 595 SignedInStateUpdated(); 596 SuggestionsUpdated(); 597 UpdateAccountChooserView(); 598 599 if (view_) 600 view_->UpdateButtonStrip(); 601 602 // On the first successful response, compute the initial user state metric. 603 if (initial_user_state_ == AutofillMetrics::DIALOG_USER_STATE_UNKNOWN) 604 initial_user_state_ = GetInitialUserState(); 605} 606 607void AutofillDialogControllerImpl::OnWalletSigninError() { 608 signin_helper_.reset(); 609 account_chooser_model_.SetHadWalletSigninError(); 610 GetWalletClient()->CancelRequests(); 611 LogDialogLatencyToShow(); 612} 613 614void AutofillDialogControllerImpl::EnsureLegalDocumentsText() { 615 if (!wallet_items_ || wallet_items_->legal_documents().empty()) 616 return; 617 618 // The text has already been constructed, no need to recompute. 619 if (!legal_documents_text_.empty()) 620 return; 621 622 const std::vector<wallet::WalletItems::LegalDocument*>& documents = 623 wallet_items_->legal_documents(); 624 DCHECK_LE(documents.size(), 3U); 625 DCHECK_GE(documents.size(), 2U); 626 const bool new_user = wallet_items_->HasRequiredAction(wallet::SETUP_WALLET); 627 628 const string16 privacy_policy_display_name = 629 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PRIVACY_POLICY_LINK); 630 string16 text; 631 if (documents.size() == 2U) { 632 text = l10n_util::GetStringFUTF16( 633 new_user ? IDS_AUTOFILL_DIALOG_LEGAL_LINKS_NEW_2 : 634 IDS_AUTOFILL_DIALOG_LEGAL_LINKS_UPDATED_2, 635 documents[0]->display_name(), 636 documents[1]->display_name()); 637 } else { 638 text = l10n_util::GetStringFUTF16( 639 new_user ? IDS_AUTOFILL_DIALOG_LEGAL_LINKS_NEW_3 : 640 IDS_AUTOFILL_DIALOG_LEGAL_LINKS_UPDATED_3, 641 documents[0]->display_name(), 642 documents[1]->display_name(), 643 documents[2]->display_name()); 644 } 645 646 legal_document_link_ranges_.clear(); 647 for (size_t i = 0; i < documents.size(); ++i) { 648 size_t link_start = text.find(documents[i]->display_name()); 649 legal_document_link_ranges_.push_back(ui::Range( 650 link_start, link_start + documents[i]->display_name().size())); 651 } 652 legal_documents_text_ = text; 653} 654 655void AutofillDialogControllerImpl::PrepareDetailInputsForSection( 656 DialogSection section) { 657 // Reset all previously entered data and stop editing |section|. 658 DetailInputs* inputs = MutableRequestedFieldsForSection(section); 659 for (size_t i = 0; i < inputs->size(); ++i) { 660 (*inputs)[i].initial_value.clear(); 661 } 662 section_editing_state_[section] = false; 663 664 // If the chosen item in |model| yields an empty suggestion text, it is 665 // invalid. In this case, show the editing UI with invalid fields highlighted. 666 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 667 if (IsASuggestionItemKey(model->GetItemKeyForCheckedItem()) && 668 SuggestionTextForSection(section).empty()) { 669 scoped_ptr<DataModelWrapper> wrapper = CreateWrapper(section); 670 wrapper->FillInputs(MutableRequestedFieldsForSection(section)); 671 section_editing_state_[section] = true; 672 } 673 674 if (view_) 675 view_->UpdateSection(section); 676} 677 678const DetailInputs& AutofillDialogControllerImpl::RequestedFieldsForSection( 679 DialogSection section) const { 680 switch (section) { 681 case SECTION_EMAIL: 682 return requested_email_fields_; 683 case SECTION_CC: 684 return requested_cc_fields_; 685 case SECTION_BILLING: 686 return requested_billing_fields_; 687 case SECTION_CC_BILLING: 688 return requested_cc_billing_fields_; 689 case SECTION_SHIPPING: 690 return requested_shipping_fields_; 691 } 692 693 NOTREACHED(); 694 return requested_billing_fields_; 695} 696 697ui::ComboboxModel* AutofillDialogControllerImpl::ComboboxModelForAutofillType( 698 AutofillFieldType type) { 699 switch (AutofillType::GetEquivalentFieldType(type)) { 700 case CREDIT_CARD_EXP_MONTH: 701 return &cc_exp_month_combobox_model_; 702 703 case CREDIT_CARD_EXP_4_DIGIT_YEAR: 704 return &cc_exp_year_combobox_model_; 705 706 case ADDRESS_HOME_COUNTRY: 707 return &country_combobox_model_; 708 709 default: 710 return NULL; 711 } 712} 713 714ui::MenuModel* AutofillDialogControllerImpl::MenuModelForSection( 715 DialogSection section) { 716 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 717 // The shipping section menu is special. It will always show because there is 718 // a choice between "Use billing" and "enter new". 719 if (section == SECTION_SHIPPING) 720 return model; 721 722 // For other sections, only show a menu if there's at least one suggestion. 723 for (int i = 0; i < model->GetItemCount(); ++i) { 724 if (IsASuggestionItemKey(model->GetItemKeyAt(i))) 725 return model; 726 } 727 728 return NULL; 729} 730 731#if defined(OS_ANDROID) 732ui::MenuModel* AutofillDialogControllerImpl::MenuModelForSectionHack( 733 DialogSection section) { 734 return SuggestionsMenuModelForSection(section); 735} 736#endif 737 738ui::MenuModel* AutofillDialogControllerImpl::MenuModelForAccountChooser() { 739 // If there were unrecoverable Wallet errors, or if there are choices other 740 // than "Pay without the wallet", show the full menu. 741 if (account_chooser_model_.had_wallet_error() || 742 account_chooser_model_.HasAccountsToChoose()) { 743 return &account_chooser_model_; 744 } 745 746 // Otherwise, there is no menu, just a sign in link. 747 return NULL; 748} 749 750gfx::Image AutofillDialogControllerImpl::AccountChooserImage() { 751 if (!MenuModelForAccountChooser()) { 752 if (signin_registrar_.IsEmpty()) { 753 return ui::ResourceBundle::GetSharedInstance().GetImageNamed( 754 IDR_WALLET_ICON); 755 } 756 757 return gfx::Image(); 758 } 759 760 gfx::Image icon; 761 account_chooser_model_.GetIconAt( 762 account_chooser_model_.GetIndexOfCommandId( 763 account_chooser_model_.checked_item()), 764 &icon); 765 return icon; 766} 767 768bool AutofillDialogControllerImpl::ShouldShowDetailArea() const { 769 // Hide the detail area when Autocheckout is running or there was an error (as 770 // there's nothing they can do after an error but cancel). 771 return !(AutocheckoutIsRunning() || HadAutocheckoutError()); 772} 773 774bool AutofillDialogControllerImpl::ShouldShowProgressBar() const { 775 // Show the progress bar while Autocheckout is running but hide it on errors, 776 // as there's no use leaving it up if the flow has failed. 777 return AutocheckoutIsRunning() && !HadAutocheckoutError(); 778} 779 780string16 AutofillDialogControllerImpl::LabelForSection(DialogSection section) 781 const { 782 switch (section) { 783 case SECTION_EMAIL: 784 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_EMAIL); 785 case SECTION_CC: 786 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_CC); 787 case SECTION_BILLING: 788 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_BILLING); 789 case SECTION_CC_BILLING: 790 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_CC_BILLING); 791 case SECTION_SHIPPING: 792 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECTION_SHIPPING); 793 default: 794 NOTREACHED(); 795 return string16(); 796 } 797} 798 799SuggestionState AutofillDialogControllerImpl::SuggestionStateForSection( 800 DialogSection section) { 801 return SuggestionState(SuggestionTextForSection(section), 802 SuggestionTextStyleForSection(section), 803 SuggestionIconForSection(section), 804 ExtraSuggestionTextForSection(section), 805 ExtraSuggestionIconForSection(section), 806 EditEnabledForSection(section)); 807} 808 809string16 AutofillDialogControllerImpl::SuggestionTextForSection( 810 DialogSection section) { 811 string16 action_text = RequiredActionTextForSection(section); 812 if (!action_text.empty()) 813 return action_text; 814 815 // When the user has clicked 'edit' or a suggestion is somehow invalid (e.g. a 816 // user selects a credit card that has expired), don't show a suggestion (even 817 // though there is a profile selected in the model). 818 if (section_editing_state_[section]) 819 return string16(); 820 821 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 822 std::string item_key = model->GetItemKeyForCheckedItem(); 823 if (item_key == kSameAsBillingKey) { 824 return l10n_util::GetStringUTF16( 825 IDS_AUTOFILL_DIALOG_USING_BILLING_FOR_SHIPPING); 826 } 827 828 if (!IsASuggestionItemKey(item_key)) 829 return string16(); 830 831 if (section == SECTION_EMAIL) 832 return model->GetLabelAt(model->checked_item()); 833 834 scoped_ptr<DataModelWrapper> wrapper = CreateWrapper(section); 835 return wrapper->GetDisplayText(); 836} 837 838gfx::Font::FontStyle 839 AutofillDialogControllerImpl::SuggestionTextStyleForSection( 840 DialogSection section) const { 841 const SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 842 if (model->GetItemKeyForCheckedItem() == kSameAsBillingKey) 843 return gfx::Font::ITALIC; 844 845 return gfx::Font::NORMAL; 846} 847 848string16 AutofillDialogControllerImpl::RequiredActionTextForSection( 849 DialogSection section) const { 850 if (section == SECTION_CC_BILLING && IsSubmitPausedOn(wallet::VERIFY_CVV)) { 851 const wallet::WalletItems::MaskedInstrument* current_instrument = 852 wallet_items_->GetInstrumentById(active_instrument_id_); 853 if (current_instrument) 854 return current_instrument->TypeAndLastFourDigits(); 855 856 DetailOutputMap output; 857 view_->GetUserInput(section, &output); 858 CreditCard card; 859 GetBillingInfoFromOutputs(output, &card, NULL, NULL); 860 return card.TypeAndLastFourDigits(); 861 } 862 863 return string16(); 864} 865 866string16 AutofillDialogControllerImpl::ExtraSuggestionTextForSection( 867 DialogSection section) const { 868 if (section == SECTION_CC || 869 (section == SECTION_CC_BILLING && IsSubmitPausedOn(wallet::VERIFY_CVV))) { 870 return l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PLACEHOLDER_CVC); 871 } 872 873 return string16(); 874} 875 876scoped_ptr<DataModelWrapper> AutofillDialogControllerImpl::CreateWrapper( 877 DialogSection section) { 878 if (IsPayingWithWallet() && full_wallet_ && 879 full_wallet_->required_actions().empty()) { 880 if (section == SECTION_CC_BILLING) { 881 return scoped_ptr<DataModelWrapper>( 882 new FullWalletBillingWrapper(full_wallet_.get())); 883 } 884 if (section == SECTION_SHIPPING) { 885 return scoped_ptr<DataModelWrapper>( 886 new FullWalletShippingWrapper(full_wallet_.get())); 887 } 888 } 889 890 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); 891 std::string item_key = model->GetItemKeyForCheckedItem(); 892 if (!IsASuggestionItemKey(item_key) || IsManuallyEditingSection(section)) 893 return scoped_ptr<DataModelWrapper>(); 894 895 if (IsPayingWithWallet()) { 896 int index; 897 bool success = base::StringToInt(item_key, &index); 898 DCHECK(success); 899 900 if (section == SECTION_CC_BILLING) { 901 return scoped_ptr<DataModelWrapper>( 902 new WalletInstrumentWrapper(wallet_items_->instruments()[index])); 903 } 904 905 if (section == SECTION_SHIPPING) { 906 return scoped_ptr<DataModelWrapper>( 907 new WalletAddressWrapper(wallet_items_->addresses()[index])); 908 } 909 910 return scoped_ptr<DataModelWrapper>(); 911 } 912 913 if (section == SECTION_CC) { 914 CreditCard* card = GetManager()->GetCreditCardByGUID(item_key); 915 DCHECK(card); 916 return scoped_ptr<DataModelWrapper>(new AutofillCreditCardWrapper(card)); 917 } 918 919 // Calculate the variant by looking at how many items come from the same 920 // data model. 921 size_t variant = 0; 922 for (int i = model->checked_item() - 1; i >= 0; --i) { 923 if (model->GetItemKeyAt(i) == item_key) 924 variant++; 925 else 926 break; 927 } 928 929 AutofillProfile* profile = GetManager()->GetProfileByGUID(item_key); 930 DCHECK(profile); 931 return scoped_ptr<DataModelWrapper>( 932 new AutofillProfileWrapper(profile, variant)); 933} 934 935gfx::Image AutofillDialogControllerImpl::SuggestionIconForSection( 936 DialogSection section) { 937 scoped_ptr<DataModelWrapper> model = CreateWrapper(section); 938 if (!model.get()) 939 return gfx::Image(); 940 941 return model->GetIcon(); 942} 943 944gfx::Image AutofillDialogControllerImpl::ExtraSuggestionIconForSection( 945 DialogSection section) const { 946 if (section == SECTION_CC || section == SECTION_CC_BILLING) 947 return IconForField(CREDIT_CARD_VERIFICATION_CODE, string16()); 948 949 return gfx::Image(); 950} 951 952bool AutofillDialogControllerImpl::EditEnabledForSection( 953 DialogSection section) const { 954 if (SuggestionsMenuModelForSection(section)->GetItemKeyForCheckedItem() == 955 kSameAsBillingKey) { 956 return false; 957 } 958 959 if (section == SECTION_CC_BILLING && IsSubmitPausedOn(wallet::VERIFY_CVV)) 960 return false; 961 962 return true; 963} 964 965void AutofillDialogControllerImpl::EditClickedForSection( 966 DialogSection section) { 967 scoped_ptr<DataModelWrapper> model = CreateWrapper(section); 968 model->FillInputs(MutableRequestedFieldsForSection(section)); 969 section_editing_state_[section] = true; 970 view_->UpdateSection(section); 971 972 GetMetricLogger().LogDialogUiEvent( 973 dialog_type_, DialogSectionToUiEditEvent(section)); 974} 975 976void AutofillDialogControllerImpl::EditCancelledForSection( 977 DialogSection section) { 978 PrepareDetailInputsForSection(section); 979} 980 981gfx::Image AutofillDialogControllerImpl::IconForField( 982 AutofillFieldType type, const string16& user_input) const { 983 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 984 if (type == CREDIT_CARD_VERIFICATION_CODE) 985 return rb.GetImageNamed(IDR_CREDIT_CARD_CVC_HINT); 986 987 // For the credit card, we show a few grayscale images, and possibly one 988 // color image if |user_input| is a valid card number. 989 if (type == CREDIT_CARD_NUMBER) { 990 const int card_idrs[] = { 991 IDR_AUTOFILL_CC_VISA, 992 IDR_AUTOFILL_CC_MASTERCARD, 993 IDR_AUTOFILL_CC_AMEX, 994 IDR_AUTOFILL_CC_DISCOVER 995 }; 996 const int number_of_cards = arraysize(card_idrs); 997 // The number of pixels between card icons. 998 const int kCardPadding = 2; 999 1000 gfx::ImageSkia some_card = *rb.GetImageSkiaNamed(card_idrs[0]); 1001 const int card_width = some_card.width(); 1002 gfx::Canvas canvas( 1003 gfx::Size((card_width + kCardPadding) * number_of_cards - kCardPadding, 1004 some_card.height()), 1005 ui::SCALE_FACTOR_100P, 1006 false); 1007 CreditCard card; 1008 card.SetRawInfo(CREDIT_CARD_NUMBER, user_input); 1009 1010 for (int i = 0; i < number_of_cards; ++i) { 1011 int idr = card_idrs[i]; 1012 gfx::ImageSkia card_image = *rb.GetImageSkiaNamed(idr); 1013 if (card.IconResourceId() != idr) { 1014 color_utils::HSL shift = {-1, 0, 0.8}; 1015 SkBitmap disabled_bitmap = 1016 SkBitmapOperations::CreateHSLShiftedBitmap(*card_image.bitmap(), 1017 shift); 1018 card_image = gfx::ImageSkia::CreateFrom1xBitmap(disabled_bitmap); 1019 } 1020 1021 canvas.DrawImageInt(card_image, i * (card_width + kCardPadding), 0); 1022 } 1023 1024 gfx::ImageSkia skia(canvas.ExtractImageRep()); 1025 return gfx::Image(skia); 1026 } 1027 1028 return gfx::Image(); 1029} 1030 1031// TODO(estade): Replace all the error messages here with more helpful and 1032// translateable ones. TODO(groby): Also add tests. 1033string16 AutofillDialogControllerImpl::InputValidityMessage( 1034 AutofillFieldType type, 1035 const string16& value) const { 1036 if (InputIsValid(type, value)) 1037 return string16(); 1038 1039 if (value.empty()) 1040 return ASCIIToUTF16("You forgot one"); 1041 1042 return ASCIIToUTF16("Are you sure this is right?"); 1043} 1044 1045// TODO(estade): Replace all the error messages here with more helpful and 1046// translateable ones. TODO(groby): Also add tests. 1047ValidityData AutofillDialogControllerImpl::InputsAreValid( 1048 const DetailOutputMap& inputs, ValidationType validation_type) const { 1049 ValidityData invalid_messages; 1050 std::map<AutofillFieldType, string16> field_values; 1051 for (DetailOutputMap::const_iterator iter = inputs.begin(); 1052 iter != inputs.end(); ++iter) { 1053 // Skip empty fields in edit mode. 1054 if (validation_type == VALIDATE_EDIT && iter->second.empty()) 1055 continue; 1056 1057 const AutofillFieldType type = iter->first->type; 1058 string16 message = InputValidityMessage(type, iter->second); 1059 if (!message.empty()) 1060 invalid_messages[type] = message; 1061 else 1062 field_values[type] = iter->second; 1063 } 1064 1065 // Validate the date formed by month and year field. (Autofill dialog is 1066 // never supposed to have 2-digit years, so not checked). 1067 if (field_values.count(CREDIT_CARD_EXP_MONTH) && 1068 field_values.count(CREDIT_CARD_EXP_4_DIGIT_YEAR)) { 1069 if (!autofill::IsValidCreditCardExpirationDate( 1070 field_values[CREDIT_CARD_EXP_4_DIGIT_YEAR], 1071 field_values[CREDIT_CARD_EXP_MONTH], 1072 base::Time::Now())) { 1073 invalid_messages[CREDIT_CARD_EXP_MONTH] = 1074 ASCIIToUTF16("more complicated message"); 1075 invalid_messages[CREDIT_CARD_EXP_4_DIGIT_YEAR] = 1076 ASCIIToUTF16("more complicated message"); 1077 } 1078 } 1079 1080 // If there is a credit card number and a CVC, validate them together. 1081 if (field_values.count(CREDIT_CARD_NUMBER) && 1082 field_values.count(CREDIT_CARD_VERIFICATION_CODE) && 1083 InputIsValid(CREDIT_CARD_NUMBER, field_values[CREDIT_CARD_NUMBER])) { 1084 if (!autofill::IsValidCreditCardSecurityCode( 1085 field_values[CREDIT_CARD_VERIFICATION_CODE], 1086 field_values[CREDIT_CARD_NUMBER])) { 1087 invalid_messages[CREDIT_CARD_VERIFICATION_CODE] = 1088 ASCIIToUTF16("CVC doesn't match card type!"); 1089 } 1090 } 1091 1092 return invalid_messages; 1093} 1094 1095void AutofillDialogControllerImpl::UserEditedOrActivatedInput( 1096 const DetailInput* input, 1097 gfx::NativeView parent_view, 1098 const gfx::Rect& content_bounds, 1099 const string16& field_contents, 1100 bool was_edit) { 1101 // If the field is edited down to empty, don't show a popup. 1102 if (was_edit && field_contents.empty()) { 1103 HidePopup(); 1104 return; 1105 } 1106 1107 // If the user clicks while the popup is already showing, be sure to hide 1108 // it. 1109 if (!was_edit && popup_controller_) { 1110 HidePopup(); 1111 return; 1112 } 1113 1114 std::vector<string16> popup_values, popup_labels, popup_icons; 1115 if (IsCreditCardType(input->type)) { 1116 GetManager()->GetCreditCardSuggestions(input->type, 1117 field_contents, 1118 &popup_values, 1119 &popup_labels, 1120 &popup_icons, 1121 &popup_guids_); 1122 } else { 1123 std::vector<AutofillFieldType> field_types; 1124 field_types.push_back(EMAIL_ADDRESS); 1125 for (DetailInputs::const_iterator iter = requested_shipping_fields_.begin(); 1126 iter != requested_shipping_fields_.end(); ++iter) { 1127 field_types.push_back(iter->type); 1128 } 1129 GetManager()->GetProfileSuggestions(input->type, 1130 field_contents, 1131 false, 1132 field_types, 1133 &popup_values, 1134 &popup_labels, 1135 &popup_icons, 1136 &popup_guids_); 1137 } 1138 1139 if (popup_values.empty()) { 1140 HidePopup(); 1141 return; 1142 } 1143 1144 // TODO(estade): do we need separators and control rows like 'Clear 1145 // Form'? 1146 std::vector<int> popup_ids; 1147 for (size_t i = 0; i < popup_guids_.size(); ++i) { 1148 popup_ids.push_back(i); 1149 } 1150 1151 popup_controller_ = AutofillPopupControllerImpl::GetOrCreate( 1152 popup_controller_, 1153 weak_ptr_factory_.GetWeakPtr(), 1154 parent_view, 1155 content_bounds); 1156 popup_controller_->Show(popup_values, 1157 popup_labels, 1158 popup_icons, 1159 popup_ids); 1160 input_showing_popup_ = input; 1161} 1162 1163void AutofillDialogControllerImpl::FocusMoved() { 1164 HidePopup(); 1165} 1166 1167void AutofillDialogControllerImpl::ViewClosed() { 1168 GetManager()->RemoveObserver(this); 1169 1170 if (AutocheckoutIsRunning() || had_autocheckout_error_) { 1171 AutofillMetrics::AutocheckoutCompletionStatus metric = 1172 AutocheckoutIsRunning() ? 1173 AutofillMetrics::AUTOCHECKOUT_SUCCEEDED : 1174 AutofillMetrics::AUTOCHECKOUT_FAILED; 1175 GetMetricLogger().LogAutocheckoutDuration( 1176 base::Time::Now() - autocheckout_started_timestamp_, 1177 metric); 1178 } 1179 1180 delete this; 1181} 1182 1183std::vector<DialogNotification> 1184 AutofillDialogControllerImpl::CurrentNotifications() const { 1185 std::vector<DialogNotification> notifications; 1186 1187 if (account_chooser_model_.had_wallet_error()) { 1188 // TODO(dbeam): pass along the Wallet error or remove from the translation. 1189 // TODO(dbeam): figure out a way to dismiss this error after a while. 1190 notifications.push_back(DialogNotification( 1191 DialogNotification::WALLET_ERROR, 1192 l10n_util::GetStringFUTF16(IDS_AUTOFILL_DIALOG_COMPLETE_WITHOUT_WALLET, 1193 ASCIIToUTF16("[Wallet-Error].")))); 1194 } else { 1195 if (IsFirstRun()) { 1196 if (SignedInState() == SIGNED_IN) { 1197 if (account_chooser_model_.WalletIsSelected() && HasCompleteWallet()) { 1198 // First run, signed in, has a complete Google Wallet. 1199 notifications.push_back(DialogNotification( 1200 DialogNotification::EXPLANATORY_MESSAGE, 1201 l10n_util::GetStringUTF16( 1202 IDS_AUTOFILL_DIALOG_DETAILS_FROM_WALLET))); 1203 } else { 1204 // First run, signed in, has an incomplete (or no) Google Wallet. 1205 DialogNotification notification( 1206 DialogNotification::WALLET_USAGE_CONFIRMATION, 1207 l10n_util::GetStringUTF16( 1208 IDS_AUTOFILL_DIALOG_SAVE_DETAILS_IN_WALLET)); 1209 notification.set_checked(account_chooser_model_.WalletIsSelected()); 1210 notification.set_interactive(!is_submitting_); 1211 notifications.push_back(notification); 1212 } 1213 } else if (account_chooser_model_.WalletIsSelected()) { 1214 // First run, not signed in, wallet promo. 1215 notifications.push_back(DialogNotification( 1216 DialogNotification::WALLET_SIGNIN_PROMO, 1217 l10n_util::GetStringUTF16( 1218 IDS_AUTOFILL_DIALOG_SIGN_IN_AND_SAVE_DETAILS))); 1219 } 1220 } else if (SignedInState() == SIGNED_IN && !HasCompleteWallet()) { 1221 // After first run, signed in. 1222 DialogNotification notification( 1223 DialogNotification::WALLET_USAGE_CONFIRMATION, 1224 l10n_util::GetStringUTF16( 1225 IDS_AUTOFILL_DIALOG_SAVE_DETAILS_IN_WALLET)); 1226 notification.set_checked(account_chooser_model_.WalletIsSelected()); 1227 notification.set_interactive(!is_submitting_); 1228 notifications.push_back(notification); 1229 } else { 1230 // If the user isn't signed in and it's after the first run, no promo. 1231 } 1232 } 1233 1234 if (RequestingCreditCardInfo() && !TransmissionWillBeSecure()) { 1235 notifications.push_back(DialogNotification( 1236 DialogNotification::SECURITY_WARNING, 1237 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_SECURITY_WARNING))); 1238 } 1239 1240 if (!invoked_from_same_origin_) { 1241 notifications.push_back(DialogNotification( 1242 DialogNotification::SECURITY_WARNING, 1243 l10n_util::GetStringFUTF16(IDS_AUTOFILL_DIALOG_SITE_WARNING, 1244 UTF8ToUTF16(source_url_.host())))); 1245 } 1246 1247 if (IsSubmitPausedOn(wallet::VERIFY_CVV)) { 1248 notifications.push_back(DialogNotification( 1249 DialogNotification::REQUIRED_ACTION, 1250 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_VERIFY_CVV))); 1251 } 1252 1253 if (had_autocheckout_error_) { 1254 notifications.push_back(DialogNotification( 1255 DialogNotification::AUTOCHECKOUT_ERROR, 1256 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_AUTOCHECKOUT_ERROR))); 1257 } 1258 1259 if (wallet_server_validation_error_) { 1260 // TODO(ahutter): L10n and UI. 1261 notifications.push_back(DialogNotification( 1262 DialogNotification::REQUIRED_ACTION, 1263 ASCIIToUTF16("New data failed validation on server side"))); 1264 } 1265 1266 return notifications; 1267} 1268 1269void AutofillDialogControllerImpl::SignInLinkClicked() { 1270 if (signin_registrar_.IsEmpty()) { 1271 // Start sign in. 1272 DCHECK(!IsPayingWithWallet()); 1273 1274 content::Source<content::NavigationController> source(view_->ShowSignIn()); 1275 signin_registrar_.Add( 1276 this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, source); 1277 view_->UpdateAccountChooser(); 1278 1279 GetMetricLogger().LogDialogUiEvent( 1280 dialog_type_, AutofillMetrics::DIALOG_UI_SIGNIN_SHOWN); 1281 } else { 1282 HideSignIn(); 1283 } 1284} 1285 1286void AutofillDialogControllerImpl::NotificationCheckboxStateChanged( 1287 DialogNotification::Type type, bool checked) { 1288 if (type == DialogNotification::WALLET_USAGE_CONFIRMATION) { 1289 if (checked) 1290 account_chooser_model_.SelectActiveWalletAccount(); 1291 else 1292 account_chooser_model_.SelectUseAutofill(); 1293 } 1294} 1295 1296void AutofillDialogControllerImpl::LegalDocumentLinkClicked( 1297 const ui::Range& range) { 1298 for (size_t i = 0; i < legal_document_link_ranges_.size(); ++i) { 1299 if (legal_document_link_ranges_[i] == range) { 1300 OpenTabWithUrl(wallet_items_->legal_documents()[i]->url()); 1301 return; 1302 } 1303 } 1304 1305 NOTREACHED(); 1306} 1307 1308void AutofillDialogControllerImpl::OnCancel() { 1309 HidePopup(); 1310 1311 // If the submit was successful, |callback_| will have already been |.Run()| 1312 // and nullified. If this is the case, no further actions are required. If 1313 // Autocheckout has an error, it's possible that the dialog will be submitted 1314 // to start the flow and then cancelled to close the dialog after the error. 1315 if (callback_.is_null()) 1316 return; 1317 1318 LogOnCancelMetrics(); 1319 1320 callback_.Run(NULL, std::string()); 1321 callback_ = base::Callback<void(const FormStructure*, const std::string&)>(); 1322} 1323 1324void AutofillDialogControllerImpl::OnAccept() { 1325 HidePopup(); 1326 SetIsSubmitting(true); 1327 if (IsSubmitPausedOn(wallet::VERIFY_CVV)) { 1328 DCHECK(!active_instrument_id_.empty()); 1329 GetWalletClient()->AuthenticateInstrument( 1330 active_instrument_id_, 1331 UTF16ToUTF8(view_->GetCvc()), 1332 wallet_items_->obfuscated_gaia_id()); 1333 } else if (IsPayingWithWallet()) { 1334 SubmitWithWallet(); 1335 } else { 1336 FinishSubmit(); 1337 } 1338} 1339 1340Profile* AutofillDialogControllerImpl::profile() { 1341 return profile_; 1342} 1343 1344content::WebContents* AutofillDialogControllerImpl::web_contents() { 1345 return contents_; 1346} 1347 1348//////////////////////////////////////////////////////////////////////////////// 1349// AutofillPopupDelegate implementation. 1350 1351void AutofillDialogControllerImpl::OnPopupShown( 1352 content::KeyboardListener* listener) { 1353 GetMetricLogger().LogDialogPopupEvent( 1354 dialog_type_, AutofillMetrics::DIALOG_POPUP_SHOWN); 1355} 1356 1357void AutofillDialogControllerImpl::OnPopupHidden( 1358 content::KeyboardListener* listener) {} 1359 1360void AutofillDialogControllerImpl::DidSelectSuggestion(int identifier) { 1361 // TODO(estade): implement. 1362} 1363 1364void AutofillDialogControllerImpl::DidAcceptSuggestion(const string16& value, 1365 int identifier) { 1366 const PersonalDataManager::GUIDPair& pair = popup_guids_[identifier]; 1367 1368 scoped_ptr<DataModelWrapper> wrapper; 1369 if (IsCreditCardType(input_showing_popup_->type)) { 1370 wrapper.reset(new AutofillCreditCardWrapper( 1371 GetManager()->GetCreditCardByGUID(pair.first))); 1372 } else { 1373 wrapper.reset(new AutofillProfileWrapper( 1374 GetManager()->GetProfileByGUID(pair.first), pair.second)); 1375 } 1376 1377 for (size_t i = SECTION_MIN; i <= SECTION_MAX; ++i) { 1378 DialogSection section = static_cast<DialogSection>(i); 1379 wrapper->FillInputs(MutableRequestedFieldsForSection(section)); 1380 view_->FillSection(section, *input_showing_popup_); 1381 } 1382 1383 GetMetricLogger().LogDialogPopupEvent( 1384 dialog_type_, AutofillMetrics::DIALOG_POPUP_FORM_FILLED); 1385 1386 // TODO(estade): not sure why it's necessary to do this explicitly. 1387 HidePopup(); 1388} 1389 1390void AutofillDialogControllerImpl::RemoveSuggestion(const string16& value, 1391 int identifier) { 1392 // TODO(estade): implement. 1393} 1394 1395void AutofillDialogControllerImpl::ClearPreviewedForm() { 1396 // TODO(estade): implement. 1397} 1398 1399//////////////////////////////////////////////////////////////////////////////// 1400// content::NotificationObserver implementation. 1401 1402void AutofillDialogControllerImpl::Observe( 1403 int type, 1404 const content::NotificationSource& source, 1405 const content::NotificationDetails& details) { 1406 DCHECK_EQ(type, content::NOTIFICATION_NAV_ENTRY_COMMITTED); 1407 content::LoadCommittedDetails* load_details = 1408 content::Details<content::LoadCommittedDetails>(details).ptr(); 1409 if (wallet::IsSignInContinueUrl(load_details->entry->GetVirtualURL())) { 1410 HideSignIn(); 1411 account_chooser_model_.SelectActiveWalletAccount(); 1412 GetWalletItems(); 1413 } 1414} 1415 1416//////////////////////////////////////////////////////////////////////////////// 1417// SuggestionsMenuModelDelegate implementation. 1418 1419void AutofillDialogControllerImpl::SuggestionItemSelected( 1420 SuggestionsMenuModel* model, 1421 size_t index) { 1422 if (model->GetItemKeyAt(index) == kManageItemsKey) { 1423 GURL url = IsPayingWithWallet() ? wallet::GetManageItemsUrl() : 1424 GURL(chrome::kChromeUISettingsURL).Resolve(chrome::kAutofillSubPage); 1425 OpenTabWithUrl(url); 1426 return; 1427 } 1428 1429 model->SetCheckedIndex(index); 1430 PrepareDetailInputsForSection(SectionForSuggestionsMenuModel(*model)); 1431 1432 LogSuggestionItemSelectedMetric(*model); 1433} 1434 1435//////////////////////////////////////////////////////////////////////////////// 1436// wallet::WalletClientDelegate implementation. 1437 1438const AutofillMetrics& AutofillDialogControllerImpl::GetMetricLogger() const { 1439 return metric_logger_; 1440} 1441 1442DialogType AutofillDialogControllerImpl::GetDialogType() const { 1443 return dialog_type_; 1444} 1445 1446std::string AutofillDialogControllerImpl::GetRiskData() const { 1447 // TODO(dbeam): Implement this. 1448 return "risky business"; 1449} 1450 1451void AutofillDialogControllerImpl::OnDidAcceptLegalDocuments() { 1452 // TODO(dbeam): Don't send risk params until legal documents are accepted: 1453 // http://crbug.com/173505 1454} 1455 1456void AutofillDialogControllerImpl::OnDidAuthenticateInstrument(bool success) { 1457 DCHECK(is_submitting_ && IsPayingWithWallet()); 1458 1459 // TODO(dbeam): use the returned full wallet. b/8332329 1460 if (success) 1461 GetFullWallet(); 1462 else 1463 DisableWallet(); 1464} 1465 1466void AutofillDialogControllerImpl::OnDidGetFullWallet( 1467 scoped_ptr<wallet::FullWallet> full_wallet) { 1468 DCHECK(is_submitting_ && IsPayingWithWallet()); 1469 1470 full_wallet_ = full_wallet.Pass(); 1471 1472 if (full_wallet_->required_actions().empty()) { 1473 FinishSubmit(); 1474 return; 1475 } 1476 1477 SuggestionsUpdated(); 1478 view_->UpdateNotificationArea(); 1479 view_->UpdateButtonStrip(); 1480} 1481 1482void AutofillDialogControllerImpl::OnPassiveSigninSuccess( 1483 const std::string& username) { 1484 const string16 username16 = UTF8ToUTF16(username); 1485 signin_helper_.reset(); 1486 account_chooser_model_.SetActiveWalletAccountName(username16); 1487 GetWalletItems(); 1488} 1489 1490void AutofillDialogControllerImpl::OnUserNameFetchSuccess( 1491 const std::string& username) { 1492 const string16 username16 = UTF8ToUTF16(username); 1493 signin_helper_.reset(); 1494 account_chooser_model_.SetActiveWalletAccountName(username16); 1495 OnWalletOrSigninUpdate(); 1496} 1497 1498void AutofillDialogControllerImpl::OnAutomaticSigninSuccess( 1499 const std::string& username) { 1500 NOTIMPLEMENTED(); 1501} 1502 1503void AutofillDialogControllerImpl::OnPassiveSigninFailure( 1504 const GoogleServiceAuthError& error) { 1505 // TODO(aruslan): report an error. 1506 LOG(ERROR) << "failed to passively sign in: " << error.ToString(); 1507 OnWalletSigninError(); 1508} 1509 1510void AutofillDialogControllerImpl::OnUserNameFetchFailure( 1511 const GoogleServiceAuthError& error) { 1512 // TODO(aruslan): report an error. 1513 LOG(ERROR) << "failed to fetch the user account name: " << error.ToString(); 1514 OnWalletSigninError(); 1515} 1516 1517void AutofillDialogControllerImpl::OnAutomaticSigninFailure( 1518 const GoogleServiceAuthError& error) { 1519 // TODO(aruslan): report an error. 1520 LOG(ERROR) << "failed to automatically sign in: " << error.ToString(); 1521 OnWalletSigninError(); 1522} 1523 1524void AutofillDialogControllerImpl::OnDidGetWalletItems( 1525 scoped_ptr<wallet::WalletItems> wallet_items) { 1526 DCHECK(account_chooser_model_.WalletIsSelected()); 1527 1528 legal_documents_text_.clear(); 1529 legal_document_link_ranges_.clear(); 1530 1531 // TODO(dbeam): verify items support kCartCurrency? http://crbug.com/232952 1532 wallet_items_ = wallet_items.Pass(); 1533 OnWalletOrSigninUpdate(); 1534} 1535 1536void AutofillDialogControllerImpl::OnDidSaveAddress( 1537 const std::string& address_id, 1538 const std::vector<wallet::RequiredAction>& required_actions) { 1539 DCHECK(is_submitting_ && IsPayingWithWallet()); 1540 1541 if (required_actions.empty()) { 1542 active_address_id_ = address_id; 1543 if (!active_instrument_id_.empty()) 1544 GetFullWallet(); 1545 } else { 1546 HandleSaveOrUpdateRequiredActions(required_actions); 1547 } 1548} 1549 1550void AutofillDialogControllerImpl::OnDidSaveInstrument( 1551 const std::string& instrument_id, 1552 const std::vector<wallet::RequiredAction>& required_actions) { 1553 DCHECK(is_submitting_ && IsPayingWithWallet()); 1554 1555 if (required_actions.empty()) { 1556 active_instrument_id_ = instrument_id; 1557 if (!active_address_id_.empty()) 1558 GetFullWallet(); 1559 } else { 1560 HandleSaveOrUpdateRequiredActions(required_actions); 1561 } 1562} 1563 1564void AutofillDialogControllerImpl::OnDidSaveInstrumentAndAddress( 1565 const std::string& instrument_id, 1566 const std::string& address_id, 1567 const std::vector<wallet::RequiredAction>& required_actions) { 1568 OnDidSaveInstrument(instrument_id, required_actions); 1569 OnDidSaveAddress(address_id, required_actions); 1570} 1571 1572void AutofillDialogControllerImpl::OnDidUpdateAddress( 1573 const std::string& address_id, 1574 const std::vector<wallet::RequiredAction>& required_actions) { 1575 OnDidSaveAddress(address_id, required_actions); 1576} 1577 1578void AutofillDialogControllerImpl::OnDidUpdateInstrument( 1579 const std::string& instrument_id, 1580 const std::vector<wallet::RequiredAction>& required_actions) { 1581 OnDidSaveInstrument(instrument_id, required_actions); 1582} 1583 1584void AutofillDialogControllerImpl::OnWalletError( 1585 wallet::WalletClient::ErrorType error_type) { 1586 // TODO(dbeam): Do something with |error_type|. http://crbug.com/164410 1587 DisableWallet(); 1588} 1589 1590void AutofillDialogControllerImpl::OnMalformedResponse() { 1591 DisableWallet(); 1592} 1593 1594void AutofillDialogControllerImpl::OnNetworkError(int response_code) { 1595 DisableWallet(); 1596} 1597 1598//////////////////////////////////////////////////////////////////////////////// 1599// PersonalDataManagerObserver implementation. 1600 1601void AutofillDialogControllerImpl::OnPersonalDataChanged() { 1602 SuggestionsUpdated(); 1603} 1604 1605//////////////////////////////////////////////////////////////////////////////// 1606// AccountChooserModelDelegate implementation. 1607 1608void AutofillDialogControllerImpl::AccountChoiceChanged() { 1609 if (is_submitting_) 1610 GetWalletClient()->CancelRequests(); 1611 1612 SetIsSubmitting(false); 1613 1614 SuggestionsUpdated(); 1615 UpdateAccountChooserView(); 1616} 1617 1618void AutofillDialogControllerImpl::UpdateAccountChooserView() { 1619 if (view_) { 1620 view_->UpdateAccountChooser(); 1621 view_->UpdateNotificationArea(); 1622 } 1623} 1624 1625//////////////////////////////////////////////////////////////////////////////// 1626 1627bool AutofillDialogControllerImpl::HandleKeyPressEventInInput( 1628 const content::NativeWebKeyboardEvent& event) { 1629 if (popup_controller_) 1630 return popup_controller_->HandleKeyPressEvent(event); 1631 1632 return false; 1633} 1634 1635bool AutofillDialogControllerImpl::RequestingCreditCardInfo() const { 1636 DCHECK_GT(form_structure_.field_count(), 0U); 1637 1638 for (size_t i = 0; i < form_structure_.field_count(); ++i) { 1639 if (IsCreditCardType(form_structure_.field(i)->type())) 1640 return true; 1641 } 1642 1643 return false; 1644} 1645 1646bool AutofillDialogControllerImpl::TransmissionWillBeSecure() const { 1647 return source_url_.SchemeIs(chrome::kHttpsScheme) && 1648 !net::IsCertStatusError(ssl_status_.cert_status) && 1649 !net::IsCertStatusMinorError(ssl_status_.cert_status); 1650} 1651 1652AutofillDialogControllerImpl::AutofillDialogControllerImpl( 1653 content::WebContents* contents, 1654 const FormData& form_structure, 1655 const GURL& source_url, 1656 const DialogType dialog_type, 1657 const base::Callback<void(const FormStructure*, 1658 const std::string&)>& callback) 1659 : profile_(Profile::FromBrowserContext(contents->GetBrowserContext())), 1660 contents_(contents), 1661 initial_user_state_(AutofillMetrics::DIALOG_USER_STATE_UNKNOWN), 1662 dialog_type_(dialog_type), 1663 form_structure_(form_structure, std::string()), 1664 invoked_from_same_origin_(true), 1665 source_url_(source_url), 1666 ssl_status_(form_structure.ssl_status), 1667 callback_(callback), 1668 account_chooser_model_(this, profile_->GetPrefs(), metric_logger_, 1669 dialog_type), 1670 wallet_client_(profile_->GetRequestContext(), this), 1671 suggested_email_(this), 1672 suggested_cc_(this), 1673 suggested_billing_(this), 1674 suggested_cc_billing_(this), 1675 suggested_shipping_(this), 1676 input_showing_popup_(NULL), 1677 weak_ptr_factory_(this), 1678 is_first_run_(!profile_->GetPrefs()->HasPrefPath( 1679 ::prefs::kAutofillDialogPayWithoutWallet)), 1680 is_submitting_(false), 1681 wallet_server_validation_error_(false), 1682 had_autocheckout_error_(false), 1683 was_ui_latency_logged_(false) { 1684 // TODO(estade): remove duplicates from |form_structure|? 1685 DCHECK(!callback_.is_null()); 1686} 1687 1688AutofillDialogView* AutofillDialogControllerImpl::CreateView() { 1689 return AutofillDialogView::Create(this); 1690} 1691 1692PersonalDataManager* AutofillDialogControllerImpl::GetManager() { 1693 return PersonalDataManagerFactory::GetForProfile(profile_); 1694} 1695 1696wallet::WalletClient* AutofillDialogControllerImpl::GetWalletClient() { 1697 return &wallet_client_; 1698} 1699 1700bool AutofillDialogControllerImpl::IsPayingWithWallet() const { 1701 return account_chooser_model_.WalletIsSelected() && 1702 SignedInState() == SIGNED_IN; 1703} 1704 1705bool AutofillDialogControllerImpl::IsFirstRun() const { 1706 return is_first_run_; 1707} 1708 1709void AutofillDialogControllerImpl::OpenTabWithUrl(const GURL& url) { 1710#if !defined(OS_ANDROID) 1711 chrome::NavigateParams params( 1712 chrome::FindBrowserWithWebContents(web_contents()), 1713 url, 1714 content::PAGE_TRANSITION_AUTO_BOOKMARK); 1715 params.disposition = NEW_FOREGROUND_TAB; 1716 chrome::Navigate(¶ms); 1717#else 1718 // TODO(estade): use TabModelList? 1719#endif 1720} 1721 1722void AutofillDialogControllerImpl::DisableWallet() { 1723 signin_helper_.reset(); 1724 account_chooser_model_.SetHadWalletError(); 1725 GetWalletClient()->CancelRequests(); 1726 wallet_items_.reset(); 1727 full_wallet_.reset(); 1728 SetIsSubmitting(false); 1729} 1730 1731void AutofillDialogControllerImpl::SuggestionsUpdated() { 1732 suggested_email_.Reset(); 1733 suggested_cc_.Reset(); 1734 suggested_billing_.Reset(); 1735 suggested_cc_billing_.Reset(); 1736 suggested_shipping_.Reset(); 1737 HidePopup(); 1738 1739 suggested_shipping_.AddKeyedItem( 1740 kSameAsBillingKey, 1741 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_USE_BILLING_FOR_SHIPPING)); 1742 1743 if (IsPayingWithWallet()) { 1744 if (!account_chooser_model_.active_wallet_account_name().empty()) { 1745 suggested_email_.AddKeyedItem( 1746 base::IntToString(0), 1747 account_chooser_model_.active_wallet_account_name()); 1748 } 1749 1750 const std::vector<wallet::Address*>& addresses = 1751 wallet_items_->addresses(); 1752 for (size_t i = 0; i < addresses.size(); ++i) { 1753 std::string key = base::IntToString(i); 1754 suggested_shipping_.AddKeyedItemWithSublabel( 1755 key, 1756 addresses[i]->DisplayName(), 1757 addresses[i]->DisplayNameDetail()); 1758 1759 if (addresses[i]->object_id() == 1760 wallet_items_->default_address_id()) { 1761 suggested_shipping_.SetCheckedItem(key); 1762 } 1763 } 1764 1765 if (!IsSubmitPausedOn(wallet::VERIFY_CVV)) { 1766 const std::vector<wallet::WalletItems::MaskedInstrument*>& instruments = 1767 wallet_items_->instruments(); 1768 for (size_t i = 0; i < instruments.size(); ++i) { 1769 std::string key = base::IntToString(i); 1770 suggested_cc_billing_.AddKeyedItemWithSublabelAndIcon( 1771 key, 1772 instruments[i]->DisplayName(), 1773 instruments[i]->DisplayNameDetail(), 1774 instruments[i]->CardIcon()); 1775 1776 if (instruments[i]->object_id() == 1777 wallet_items_->default_instrument_id()) { 1778 suggested_cc_billing_.SetCheckedItem(key); 1779 } 1780 } 1781 1782 // TODO(estade): this should have a URL sublabel. 1783 suggested_cc_billing_.AddKeyedItem( 1784 kAddNewItemKey, 1785 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_BILLING_DETAILS)); 1786 suggested_cc_billing_.AddKeyedItem( 1787 kManageItemsKey, 1788 l10n_util::GetStringUTF16( 1789 IDS_AUTOFILL_DIALOG_MANAGE_BILLING_DETAILS)); 1790 } 1791 } else { 1792 PersonalDataManager* manager = GetManager(); 1793 const std::vector<CreditCard*>& cards = manager->GetCreditCards(); 1794 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 1795 for (size_t i = 0; i < cards.size(); ++i) { 1796 suggested_cc_.AddKeyedItemWithIcon( 1797 cards[i]->guid(), 1798 cards[i]->Label(), 1799 rb.GetImageNamed(cards[i]->IconResourceId())); 1800 } 1801 1802 const std::vector<AutofillProfile*>& profiles = manager->GetProfiles(); 1803 const std::string app_locale = g_browser_process->GetApplicationLocale(); 1804 for (size_t i = 0; i < profiles.size(); ++i) { 1805 if (!IsCompleteProfile(*profiles[i])) 1806 continue; 1807 1808 // Add all email addresses. 1809 std::vector<string16> values; 1810 profiles[i]->GetMultiInfo(EMAIL_ADDRESS, app_locale, &values); 1811 for (size_t j = 0; j < values.size(); ++j) { 1812 if (!values[j].empty()) 1813 suggested_email_.AddKeyedItem(profiles[i]->guid(), values[j]); 1814 } 1815 1816 // Don't add variants for addresses: the email variants are handled above, 1817 // name is part of credit card and we'll just ignore phone number 1818 // variants. 1819 suggested_billing_.AddKeyedItem(profiles[i]->guid(), 1820 profiles[i]->Label()); 1821 suggested_shipping_.AddKeyedItem(profiles[i]->guid(), 1822 profiles[i]->Label()); 1823 } 1824 1825 suggested_cc_.AddKeyedItem( 1826 kAddNewItemKey, 1827 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_CREDIT_CARD)); 1828 suggested_cc_.AddKeyedItem( 1829 kManageItemsKey, 1830 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MANAGE_CREDIT_CARD)); 1831 suggested_billing_.AddKeyedItem( 1832 kAddNewItemKey, 1833 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_BILLING_ADDRESS)); 1834 suggested_billing_.AddKeyedItem( 1835 kManageItemsKey, 1836 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MANAGE_BILLING_ADDRESS)); 1837 } 1838 1839 suggested_email_.AddKeyedItem( 1840 kAddNewItemKey, 1841 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_EMAIL_ADDRESS)); 1842 if (!IsPayingWithWallet()) { 1843 suggested_email_.AddKeyedItem( 1844 kManageItemsKey, 1845 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MANAGE_EMAIL_ADDRESS)); 1846 } 1847 1848 suggested_shipping_.AddKeyedItem( 1849 kAddNewItemKey, 1850 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_SHIPPING_ADDRESS)); 1851 suggested_shipping_.AddKeyedItem( 1852 kManageItemsKey, 1853 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_MANAGE_SHIPPING_ADDRESS)); 1854 1855 if (!IsPayingWithWallet()) { 1856 // When using Autofill, the default option is the first suggestion, if 1857 // one exists. Otherwise it's the "Use shipping for billing" item. 1858 const std::string& first_real_suggestion_item_key = 1859 suggested_shipping_.GetItemKeyAt(1); 1860 if (IsASuggestionItemKey(first_real_suggestion_item_key)) 1861 suggested_shipping_.SetCheckedItem(first_real_suggestion_item_key); 1862 } 1863 1864 if (view_) 1865 view_->ModelChanged(); 1866 1867 for (size_t section = SECTION_MIN; section <= SECTION_MAX; ++section) { 1868 PrepareDetailInputsForSection(static_cast<DialogSection>(section)); 1869 } 1870} 1871 1872bool AutofillDialogControllerImpl::IsCompleteProfile( 1873 const AutofillProfile& profile) { 1874 const std::string app_locale = g_browser_process->GetApplicationLocale(); 1875 for (size_t i = 0; i < requested_shipping_fields_.size(); ++i) { 1876 AutofillFieldType type = requested_shipping_fields_[i].type; 1877 if (type != ADDRESS_HOME_LINE2 && 1878 profile.GetInfo(type, app_locale).empty()) { 1879 return false; 1880 } 1881 } 1882 1883 return true; 1884} 1885 1886void AutofillDialogControllerImpl::FillOutputForSectionWithComparator( 1887 DialogSection section, 1888 const InputFieldComparator& compare) { 1889 // Email is hidden while using Wallet, special case it. 1890 if (section == SECTION_EMAIL && IsPayingWithWallet()) { 1891 AutofillProfile profile; 1892 profile.SetRawInfo(EMAIL_ADDRESS, 1893 account_chooser_model_.active_wallet_account_name()); 1894 FillFormStructureForSection(profile, 0, section, compare); 1895 return; 1896 } 1897 1898 if (!SectionIsActive(section)) 1899 return; 1900 1901 scoped_ptr<DataModelWrapper> wrapper = CreateWrapper(section); 1902 if (wrapper) { 1903 // Only fill in data that is associated with this section. 1904 const DetailInputs& inputs = RequestedFieldsForSection(section); 1905 wrapper->FillFormStructure(inputs, compare, &form_structure_); 1906 1907 // CVC needs special-casing because the CreditCard class doesn't store or 1908 // handle them. This isn't necessary when filling the combined CC and 1909 // billing section as CVC comes from |full_wallet_| in this case. 1910 if (section == SECTION_CC) 1911 SetCvcResult(view_->GetCvc()); 1912 } else { 1913 // The user manually input data. If using Autofill, save the info as new or 1914 // edited data. Always fill local data into |form_structure_|. 1915 DetailOutputMap output; 1916 view_->GetUserInput(section, &output); 1917 1918 if (section == SECTION_CC) { 1919 CreditCard card; 1920 FillFormGroupFromOutputs(output, &card); 1921 1922 if (ShouldSaveDetailsLocally()) 1923 GetManager()->SaveImportedCreditCard(card); 1924 1925 FillFormStructureForSection(card, 0, section, compare); 1926 1927 // Again, CVC needs special-casing. Fill it in directly from |output|. 1928 SetCvcResult(GetValueForType(output, CREDIT_CARD_VERIFICATION_CODE)); 1929 } else { 1930 AutofillProfile profile; 1931 FillFormGroupFromOutputs(output, &profile); 1932 1933 // For billing, the profile name has to come from the CC section. 1934 if (section == SECTION_BILLING) 1935 profile.SetRawInfo(NAME_FULL, GetCcName()); 1936 1937 if (ShouldSaveDetailsLocally()) 1938 GetManager()->SaveImportedProfile(profile); 1939 1940 FillFormStructureForSection(profile, 0, section, compare); 1941 } 1942 } 1943} 1944 1945void AutofillDialogControllerImpl::FillOutputForSection(DialogSection section) { 1946 FillOutputForSectionWithComparator(section, 1947 base::Bind(DetailInputMatchesField)); 1948} 1949 1950void AutofillDialogControllerImpl::FillFormStructureForSection( 1951 const AutofillDataModel& data_model, 1952 size_t variant, 1953 DialogSection section, 1954 const InputFieldComparator& compare) { 1955 std::string app_locale = g_browser_process->GetApplicationLocale(); 1956 for (size_t i = 0; i < form_structure_.field_count(); ++i) { 1957 AutofillField* field = form_structure_.field(i); 1958 // Only fill in data that is associated with this section. 1959 const DetailInputs& inputs = RequestedFieldsForSection(section); 1960 for (size_t j = 0; j < inputs.size(); ++j) { 1961 if (compare.Run(inputs[j], *field)) { 1962 data_model.FillFormField(*field, variant, app_locale, field); 1963 break; 1964 } 1965 } 1966 } 1967} 1968 1969void AutofillDialogControllerImpl::SetCvcResult(const string16& cvc) { 1970 for (size_t i = 0; i < form_structure_.field_count(); ++i) { 1971 AutofillField* field = form_structure_.field(i); 1972 if (field->type() == CREDIT_CARD_VERIFICATION_CODE) { 1973 field->value = cvc; 1974 break; 1975 } 1976 } 1977} 1978 1979string16 AutofillDialogControllerImpl::GetCcName() { 1980 DCHECK(SectionIsActive(SECTION_CC)); 1981 1982 CreditCard card; 1983 scoped_ptr<DataModelWrapper> wrapper = CreateWrapper(SECTION_CC); 1984 if (!wrapper) { 1985 DetailOutputMap output; 1986 view_->GetUserInput(SECTION_CC, &output); 1987 FillFormGroupFromOutputs(output, &card); 1988 wrapper.reset(new AutofillCreditCardWrapper(&card)); 1989 } 1990 1991 return wrapper->GetInfo(CREDIT_CARD_NAME); 1992} 1993 1994SuggestionsMenuModel* AutofillDialogControllerImpl:: 1995 SuggestionsMenuModelForSection(DialogSection section) { 1996 switch (section) { 1997 case SECTION_EMAIL: 1998 return &suggested_email_; 1999 case SECTION_CC: 2000 return &suggested_cc_; 2001 case SECTION_BILLING: 2002 return &suggested_billing_; 2003 case SECTION_SHIPPING: 2004 return &suggested_shipping_; 2005 case SECTION_CC_BILLING: 2006 return &suggested_cc_billing_; 2007 } 2008 2009 NOTREACHED(); 2010 return NULL; 2011} 2012 2013const SuggestionsMenuModel* AutofillDialogControllerImpl:: 2014 SuggestionsMenuModelForSection(DialogSection section) const { 2015 return const_cast<AutofillDialogControllerImpl*>(this)-> 2016 SuggestionsMenuModelForSection(section); 2017} 2018 2019DialogSection AutofillDialogControllerImpl::SectionForSuggestionsMenuModel( 2020 const SuggestionsMenuModel& model) { 2021 if (&model == &suggested_email_) 2022 return SECTION_EMAIL; 2023 2024 if (&model == &suggested_cc_) 2025 return SECTION_CC; 2026 2027 if (&model == &suggested_billing_) 2028 return SECTION_BILLING; 2029 2030 if (&model == &suggested_cc_billing_) 2031 return SECTION_CC_BILLING; 2032 2033 DCHECK_EQ(&model, &suggested_shipping_); 2034 return SECTION_SHIPPING; 2035} 2036 2037DetailInputs* AutofillDialogControllerImpl::MutableRequestedFieldsForSection( 2038 DialogSection section) { 2039 return const_cast<DetailInputs*>(&RequestedFieldsForSection(section)); 2040} 2041 2042void AutofillDialogControllerImpl::HidePopup() { 2043 if (popup_controller_) 2044 popup_controller_->Hide(); 2045 input_showing_popup_ = NULL; 2046} 2047 2048void AutofillDialogControllerImpl::LoadRiskFingerprintData() { 2049 // TODO(dbeam): Add a CHECK or otherwise strong guarantee that the ToS have 2050 // been accepted prior to calling into this method. Also, ensure that the UI 2051 // contains a clear indication to the user as to what data will be collected. 2052 // Until then, this code should not be called. http://crbug.com/173505 2053 2054 int64 gaia_id = 0; 2055 bool success = 2056 base::StringToInt64(wallet_items_->obfuscated_gaia_id(), &gaia_id); 2057 DCHECK(success); 2058 2059 gfx::Rect window_bounds = 2060 GetBaseWindowForWebContents(web_contents())->GetBounds(); 2061 2062 PrefService* user_prefs = profile_->GetPrefs(); 2063 std::string charset = user_prefs->GetString(::prefs::kDefaultCharset); 2064 std::string accept_languages = 2065 user_prefs->GetString(::prefs::kAcceptLanguages); 2066 base::Time install_time = base::Time::FromTimeT( 2067 g_browser_process->local_state()->GetInt64(::prefs::kInstallDate)); 2068 2069 risk::GetFingerprint( 2070 gaia_id, window_bounds, *web_contents(), chrome::VersionInfo().Version(), 2071 charset, accept_languages, install_time, dialog_type_, 2072 g_browser_process->GetApplicationLocale(), 2073 base::Bind(&AutofillDialogControllerImpl::OnDidLoadRiskFingerprintData, 2074 weak_ptr_factory_.GetWeakPtr())); 2075} 2076 2077void AutofillDialogControllerImpl::OnDidLoadRiskFingerprintData( 2078 scoped_ptr<risk::Fingerprint> fingerprint) { 2079 NOTIMPLEMENTED(); 2080} 2081 2082bool AutofillDialogControllerImpl::IsManuallyEditingSection( 2083 DialogSection section) const { 2084 std::map<DialogSection, bool>::const_iterator it = 2085 section_editing_state_.find(section); 2086 return (it != section_editing_state_.end() && it->second) || 2087 SuggestionsMenuModelForSection(section)-> 2088 GetItemKeyForCheckedItem() == kAddNewItemKey; 2089} 2090 2091bool AutofillDialogControllerImpl::IsASuggestionItemKey( 2092 const std::string& key) { 2093 return !key.empty() && 2094 key != kAddNewItemKey && 2095 key != kManageItemsKey && 2096 key != kSameAsBillingKey; 2097} 2098 2099bool AutofillDialogControllerImpl::IsManuallyEditingAnySection() const { 2100 for (size_t section = SECTION_MIN; section <= SECTION_MAX; ++section) { 2101 if (IsManuallyEditingSection(static_cast<DialogSection>(section))) 2102 return true; 2103 } 2104 return false; 2105} 2106 2107bool AutofillDialogControllerImpl::InputIsValid(AutofillFieldType type, 2108 const string16& value) const { 2109 switch (AutofillType::GetEquivalentFieldType(type)) { 2110 case EMAIL_ADDRESS: 2111 return IsValidEmailAddress(value); 2112 2113 case CREDIT_CARD_NUMBER: 2114 return autofill::IsValidCreditCardNumber(value); 2115 case CREDIT_CARD_NAME: 2116 break; 2117 case CREDIT_CARD_EXP_MONTH: 2118 case CREDIT_CARD_EXP_4_DIGIT_YEAR: 2119 break; 2120 case CREDIT_CARD_VERIFICATION_CODE: 2121 return autofill::IsValidCreditCardSecurityCode(value); 2122 2123 case ADDRESS_HOME_LINE1: 2124 break; 2125 case ADDRESS_HOME_LINE2: 2126 return true; // Line 2 is optional - always valid. 2127 case ADDRESS_HOME_CITY: 2128 case ADDRESS_HOME_STATE: 2129 case ADDRESS_HOME_ZIP: 2130 case ADDRESS_HOME_COUNTRY: 2131 break; 2132 2133 case NAME_FULL: // Used for shipping. 2134 break; 2135 2136 case PHONE_HOME_WHOLE_NUMBER: // Used in billing section. 2137 // TODO(dbeam): validate with libphonenumber. 2138 break; 2139 2140 default: 2141 NOTREACHED(); // Trying to validate unknown field. 2142 break; 2143 } 2144 2145 return !value.empty(); 2146} 2147 2148bool AutofillDialogControllerImpl::AllSectionsAreValid() const { 2149 for (size_t section = SECTION_MIN; section <= SECTION_MAX; ++section) { 2150 if (!SectionIsValid(static_cast<DialogSection>(section))) 2151 return false; 2152 } 2153 return true; 2154} 2155 2156bool AutofillDialogControllerImpl::SectionIsValid( 2157 DialogSection section) const { 2158 if (!IsManuallyEditingSection(section)) 2159 return true; 2160 2161 DetailOutputMap detail_outputs; 2162 view_->GetUserInput(section, &detail_outputs); 2163 return InputsAreValid(detail_outputs, VALIDATE_EDIT).empty(); 2164} 2165 2166bool AutofillDialogControllerImpl::ShouldUseBillingForShipping() { 2167 return suggested_shipping_.GetItemKeyForCheckedItem() == kSameAsBillingKey; 2168} 2169 2170bool AutofillDialogControllerImpl::ShouldSaveDetailsLocally() { 2171 // It's possible that the user checked [X] Save details locally before 2172 // switching payment methods, so only ask the view whether to save details 2173 // locally if that checkbox is showing (currently if not paying with wallet). 2174 // Also, if the user isn't editing any sections, there's no data to save 2175 // locally. 2176 return ShouldOfferToSaveInChrome() && view_->SaveDetailsLocally(); 2177} 2178 2179void AutofillDialogControllerImpl::SetIsSubmitting(bool submitting) { 2180 is_submitting_ = submitting; 2181 2182 if (view_) { 2183 view_->UpdateButtonStrip(); 2184 view_->UpdateNotificationArea(); 2185 } 2186} 2187 2188void AutofillDialogControllerImpl::SubmitWithWallet() { 2189 // TODO(dbeam): disallow interacting with the dialog while submitting. 2190 // http://crbug.com/230932 2191 2192 active_instrument_id_.clear(); 2193 active_address_id_.clear(); 2194 full_wallet_.reset(); 2195 2196 GetWalletClient()->AcceptLegalDocuments( 2197 wallet_items_->legal_documents(), 2198 wallet_items_->google_transaction_id(), 2199 source_url_); 2200 2201 SuggestionsMenuModel* billing = 2202 SuggestionsMenuModelForSection(SECTION_CC_BILLING); 2203 int instrument_index = -1; 2204 base::StringToInt(billing->GetItemKeyForCheckedItem(), &instrument_index); 2205 2206 if (!IsManuallyEditingSection(SECTION_CC_BILLING)) { 2207 active_instrument_id_ = 2208 wallet_items_->instruments()[instrument_index]->object_id(); 2209 DCHECK(!active_instrument_id_.empty()); 2210 } 2211 2212 SuggestionsMenuModel* shipping = 2213 SuggestionsMenuModelForSection(SECTION_SHIPPING); 2214 int address_index = -1; 2215 base::StringToInt(shipping->GetItemKeyForCheckedItem(), &address_index); 2216 2217 if (!IsManuallyEditingSection(SECTION_SHIPPING) && 2218 shipping->GetItemKeyForCheckedItem() != kSameAsBillingKey) { 2219 active_address_id_ = 2220 wallet_items_->addresses()[address_index]->object_id(); 2221 DCHECK(!active_address_id_.empty()); 2222 } 2223 2224 if (!active_instrument_id_.empty() && !active_address_id_.empty()) { 2225 GetFullWallet(); 2226 return; 2227 } 2228 2229 scoped_ptr<wallet::Instrument> inputted_instrument = 2230 CreateTransientInstrument(); 2231 scoped_ptr<wallet::WalletClient::UpdateInstrumentRequest> update_request = 2232 CreateUpdateInstrumentRequest( 2233 inputted_instrument.get(), 2234 !section_editing_state_[SECTION_CC_BILLING] ? std::string() : 2235 wallet_items_->instruments()[instrument_index]->object_id()); 2236 2237 scoped_ptr<wallet::Address> inputted_address; 2238 if (active_address_id_.empty()) { 2239 if (ShouldUseBillingForShipping()) { 2240 inputted_address.reset(new wallet::Address(inputted_instrument ? 2241 inputted_instrument->address() : 2242 wallet_items_->instruments()[instrument_index]->address())); 2243 DCHECK(inputted_address->object_id().empty()); 2244 } else { 2245 inputted_address = CreateTransientAddress(); 2246 if (section_editing_state_[SECTION_SHIPPING]) { 2247 inputted_address->set_object_id( 2248 wallet_items_->addresses()[address_index]->object_id()); 2249 DCHECK(!inputted_address->object_id().empty()); 2250 } 2251 } 2252 } 2253 2254 // If instrument and address aren't based off of any existing data, save both. 2255 if (inputted_instrument && inputted_address && !update_request && 2256 inputted_address->object_id().empty()) { 2257 GetWalletClient()->SaveInstrumentAndAddress( 2258 *inputted_instrument, 2259 *inputted_address, 2260 wallet_items_->obfuscated_gaia_id(), 2261 source_url_); 2262 return; 2263 } 2264 2265 if (inputted_instrument) { 2266 if (update_request) { 2267 scoped_ptr<wallet::Address> billing_address( 2268 new wallet::Address(inputted_instrument->address())); 2269 GetWalletClient()->UpdateInstrument(*update_request, 2270 billing_address.Pass()); 2271 } else { 2272 GetWalletClient()->SaveInstrument(*inputted_instrument, 2273 wallet_items_->obfuscated_gaia_id(), 2274 source_url_); 2275 } 2276 } 2277 2278 if (inputted_address) { 2279 if (!inputted_address->object_id().empty()) 2280 GetWalletClient()->UpdateAddress(*inputted_address, source_url_); 2281 else 2282 GetWalletClient()->SaveAddress(*inputted_address, source_url_); 2283 } 2284} 2285 2286scoped_ptr<wallet::Instrument> AutofillDialogControllerImpl:: 2287 CreateTransientInstrument() { 2288 if (!active_instrument_id_.empty()) 2289 return scoped_ptr<wallet::Instrument>(); 2290 2291 DetailOutputMap output; 2292 view_->GetUserInput(SECTION_CC_BILLING, &output); 2293 2294 CreditCard card; 2295 AutofillProfile profile; 2296 string16 cvc; 2297 GetBillingInfoFromOutputs(output, &card, &cvc, &profile); 2298 2299 return scoped_ptr<wallet::Instrument>( 2300 new wallet::Instrument(card, cvc, profile)); 2301} 2302 2303scoped_ptr<wallet::WalletClient::UpdateInstrumentRequest> 2304 AutofillDialogControllerImpl::CreateUpdateInstrumentRequest( 2305 const wallet::Instrument* instrument, 2306 const std::string& instrument_id) { 2307 if (!instrument || instrument_id.empty()) 2308 return scoped_ptr<wallet::WalletClient::UpdateInstrumentRequest>(); 2309 2310 scoped_ptr<wallet::WalletClient::UpdateInstrumentRequest> update_request( 2311 new wallet::WalletClient::UpdateInstrumentRequest( 2312 instrument_id, source_url_)); 2313 update_request->expiration_month = instrument->expiration_month(); 2314 update_request->expiration_year = instrument->expiration_year(); 2315 update_request->card_verification_number = 2316 UTF16ToUTF8(instrument->card_verification_number()); 2317 update_request->obfuscated_gaia_id = wallet_items_->obfuscated_gaia_id(); 2318 return update_request.Pass(); 2319} 2320 2321scoped_ptr<wallet::Address>AutofillDialogControllerImpl:: 2322 CreateTransientAddress() { 2323 // If not using billing for shipping, just scrape the view. 2324 DetailOutputMap output; 2325 view_->GetUserInput(SECTION_SHIPPING, &output); 2326 2327 AutofillProfile profile; 2328 FillFormGroupFromOutputs(output, &profile); 2329 2330 return scoped_ptr<wallet::Address>(new wallet::Address(profile)); 2331} 2332 2333void AutofillDialogControllerImpl::GetFullWallet() { 2334 DCHECK(is_submitting_); 2335 DCHECK(IsPayingWithWallet()); 2336 DCHECK(wallet_items_); 2337 DCHECK(!active_instrument_id_.empty()); 2338 DCHECK(!active_address_id_.empty()); 2339 2340 std::vector<wallet::WalletClient::RiskCapability> capabilities; 2341 capabilities.push_back(wallet::WalletClient::VERIFY_CVC); 2342 2343 GetWalletClient()->GetFullWallet(wallet::WalletClient::FullWalletRequest( 2344 active_instrument_id_, 2345 active_address_id_, 2346 source_url_, 2347 wallet::Cart(base::IntToString(kCartMax), kCartCurrency), 2348 wallet_items_->google_transaction_id(), 2349 capabilities)); 2350} 2351 2352void AutofillDialogControllerImpl::HandleSaveOrUpdateRequiredActions( 2353 const std::vector<wallet::RequiredAction>& required_actions) { 2354 DCHECK(!required_actions.empty()); 2355 2356 for (std::vector<wallet::RequiredAction>::const_iterator iter = 2357 required_actions.begin(); 2358 iter != required_actions.end(); ++iter) { 2359 if (*iter == wallet::INVALID_FORM_FIELD) { 2360 wallet_server_validation_error_ = true; 2361 } else { 2362 // TODO(dbeam): handle this more gracefully. 2363 DisableWallet(); 2364 } 2365 } 2366 2367 SetIsSubmitting(false); 2368} 2369 2370void AutofillDialogControllerImpl::FinishSubmit() { 2371 FillOutputForSection(SECTION_EMAIL); 2372 FillOutputForSection(SECTION_CC); 2373 FillOutputForSection(SECTION_BILLING); 2374 FillOutputForSection(SECTION_CC_BILLING); 2375 2376 if (ShouldUseBillingForShipping()) { 2377 FillOutputForSectionWithComparator( 2378 SECTION_BILLING, 2379 base::Bind(DetailInputMatchesShippingField)); 2380 FillOutputForSectionWithComparator( 2381 SECTION_CC, 2382 base::Bind(DetailInputMatchesShippingField)); 2383 FillOutputForSectionWithComparator( 2384 SECTION_CC_BILLING, 2385 base::Bind(DetailInputMatchesShippingField)); 2386 } else { 2387 FillOutputForSection(SECTION_SHIPPING); 2388 } 2389 2390 callback_.Run(&form_structure_, !wallet_items_ ? std::string() : 2391 wallet_items_->google_transaction_id()); 2392 callback_ = base::Callback<void(const FormStructure*, const std::string&)>(); 2393 2394 LogOnFinishSubmitMetrics(); 2395 2396 // On a successful submit, if the user manually selected "pay without wallet", 2397 // stop trying to pay with Wallet on future runs of the dialog. 2398 bool manually_selected_pay_without_wallet = 2399 !account_chooser_model_.WalletIsSelected() && 2400 !account_chooser_model_.had_wallet_error(); 2401 profile_->GetPrefs()->SetBoolean(::prefs::kAutofillDialogPayWithoutWallet, 2402 manually_selected_pay_without_wallet); 2403 2404 switch (dialog_type_) { 2405 case DIALOG_TYPE_AUTOCHECKOUT: 2406 // Stop observing PersonalDataManager to avoid the dialog redrawing while 2407 // in an Autocheckout flow. 2408 GetManager()->RemoveObserver(this); 2409 autocheckout_started_timestamp_ = base::Time::Now(); 2410 view_->UpdateButtonStrip(); 2411 view_->UpdateDetailArea(); 2412 break; 2413 2414 case DIALOG_TYPE_REQUEST_AUTOCOMPLETE: 2415 // This may delete us. 2416 Hide(); 2417 break; 2418 } 2419} 2420 2421void AutofillDialogControllerImpl::LogOnFinishSubmitMetrics() { 2422 GetMetricLogger().LogDialogUiDuration( 2423 base::Time::Now() - dialog_shown_timestamp_, 2424 dialog_type_, 2425 AutofillMetrics::DIALOG_ACCEPTED); 2426 2427 GetMetricLogger().LogDialogUiEvent( 2428 dialog_type_, AutofillMetrics::DIALOG_UI_ACCEPTED); 2429 2430 AutofillMetrics::DialogDismissalState dismissal_state; 2431 if (!IsManuallyEditingAnySection()) 2432 dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_EXISTING_DATA; 2433 else if (IsPayingWithWallet()) 2434 dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_SAVE_TO_WALLET; 2435 else if (ShouldSaveDetailsLocally()) 2436 dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_SAVE_TO_AUTOFILL; 2437 else 2438 dismissal_state = AutofillMetrics::DIALOG_ACCEPTED_NO_SAVE; 2439 2440 GetMetricLogger().LogDialogDismissalState(dialog_type_, dismissal_state); 2441} 2442 2443void AutofillDialogControllerImpl::LogOnCancelMetrics() { 2444 GetMetricLogger().LogDialogUiEvent( 2445 dialog_type_, AutofillMetrics::DIALOG_UI_CANCELED); 2446 2447 AutofillMetrics::DialogDismissalState dismissal_state; 2448 if (!IsManuallyEditingAnySection()) 2449 dismissal_state = AutofillMetrics::DIALOG_CANCELED_NO_EDITS; 2450 else if (AllSectionsAreValid()) 2451 dismissal_state = AutofillMetrics::DIALOG_CANCELED_NO_INVALID_FIELDS; 2452 else 2453 dismissal_state = AutofillMetrics::DIALOG_CANCELED_WITH_INVALID_FIELDS; 2454 2455 GetMetricLogger().LogDialogDismissalState(dialog_type_, dismissal_state); 2456 2457 GetMetricLogger().LogDialogUiDuration( 2458 base::Time::Now() - dialog_shown_timestamp_, 2459 dialog_type_, 2460 AutofillMetrics::DIALOG_CANCELED); 2461} 2462 2463void AutofillDialogControllerImpl::LogSuggestionItemSelectedMetric( 2464 const SuggestionsMenuModel& model) { 2465 DialogSection section = SectionForSuggestionsMenuModel(model); 2466 2467 AutofillMetrics::DialogUiEvent dialog_ui_event; 2468 if (model.GetItemKeyForCheckedItem() == kAddNewItemKey) { 2469 // Selected to add a new item. 2470 dialog_ui_event = DialogSectionToUiItemAddedEvent(section); 2471 } else if (IsASuggestionItemKey(model.GetItemKeyForCheckedItem())) { 2472 // Selected an existing item. 2473 dialog_ui_event = DialogSectionToUiSelectionChangedEvent(section); 2474 } else { 2475 // TODO(estade): add logging for "Manage items" or "Use billing for 2476 // shipping"? 2477 return; 2478 } 2479 2480 GetMetricLogger().LogDialogUiEvent(dialog_type_, dialog_ui_event); 2481} 2482 2483void AutofillDialogControllerImpl::LogDialogLatencyToShow() { 2484 if (was_ui_latency_logged_) 2485 return; 2486 2487 GetMetricLogger().LogDialogLatencyToShow( 2488 dialog_type_, 2489 base::Time::Now() - dialog_shown_timestamp_); 2490 was_ui_latency_logged_ = true; 2491} 2492 2493AutofillMetrics::DialogInitialUserStateMetric 2494 AutofillDialogControllerImpl::GetInitialUserState() const { 2495 // Consider a user to be an Autofill user if the user has any credit cards 2496 // or addresses saved. Check that the item count is greater than 2 because 2497 // an "empty" menu still has the "add new" menu item and "manage" menu item. 2498 const bool has_autofill_profiles = 2499 suggested_cc_.GetItemCount() > 2 || 2500 suggested_billing_.GetItemCount() > 2; 2501 2502 if (SignedInState() != SIGNED_IN) { 2503 // Not signed in. 2504 return has_autofill_profiles ? 2505 AutofillMetrics::DIALOG_USER_NOT_SIGNED_IN_HAS_AUTOFILL : 2506 AutofillMetrics::DIALOG_USER_NOT_SIGNED_IN_NO_AUTOFILL; 2507 } 2508 2509 // Signed in. 2510 if (wallet_items_->instruments().empty()) { 2511 // No Wallet items. 2512 return has_autofill_profiles ? 2513 AutofillMetrics::DIALOG_USER_SIGNED_IN_NO_WALLET_HAS_AUTOFILL : 2514 AutofillMetrics::DIALOG_USER_SIGNED_IN_NO_WALLET_NO_AUTOFILL; 2515 } 2516 2517 // Has Wallet items. 2518 return has_autofill_profiles ? 2519 AutofillMetrics::DIALOG_USER_SIGNED_IN_HAS_WALLET_HAS_AUTOFILL : 2520 AutofillMetrics::DIALOG_USER_SIGNED_IN_HAS_WALLET_NO_AUTOFILL; 2521} 2522 2523} // namespace autofill 2524