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