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