blob: 9db3e437f76f165ddb87812bba1797e722937ae3 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/android/autofill/autofill_dialog_controller_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/jni_string.h"
#include "base/android/scoped_java_ref.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/autofill/personal_data_manager_factory.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/android/autofill/autofill_dialog_result.h"
#include "chrome/browser/ui/android/window_android_helper.h"
#include "chrome/browser/ui/autofill/autofill_dialog_common.h"
#include "chrome/browser/ui/autofill/data_model_wrapper.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/autofill/content/browser/wallet/full_wallet.h"
#include "components/autofill/core/browser/autofill_metrics.h"
#include "components/autofill/core/browser/autofill_profile.h"
#include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/credit_card.h"
#include "components/autofill/core/browser/personal_data_manager.h"
#include "components/autofill/core/common/form_data.h"
#include "components/user_prefs/pref_registry_syncable.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "grit/generated_resources.h"
#include "jni/AutofillDialogControllerAndroid_jni.h"
#include "ui/android/window_android.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/combobox_model.h"
#include "ui/base/models/menu_model.h"
#include "ui/gfx/android/java_bitmap.h"
#include "ui/gfx/rect.h"
#include "url/gurl.h"
namespace autofill {
namespace {
// Keys in kAutofillDialogDefaults pref dictionary (do not change these values).
const char kLastUsedAccountName[] = "last_used_account_name";
const char kLastUsedChoiceIsAutofill[] = "last_used_choice_is_autofill";
const char kLastUsedBillingAddressGuid[] = "last_used_billing";
const char kLastUsedShippingAddressGuid[] = "last_used_shipping";
const char kLastUsedCreditCardGuid[] = "last_used_card";
scoped_ptr<DataModelWrapper> CreateWrapper(
DialogSection section, wallet::FullWallet* full_wallet) {
if (section == SECTION_CC_BILLING) {
if (!full_wallet->billing_address())
return scoped_ptr<DataModelWrapper>();
return scoped_ptr<DataModelWrapper>(
new FullWalletBillingWrapper(full_wallet));
}
if (section == SECTION_SHIPPING) {
if (!full_wallet->shipping_address())
return scoped_ptr<DataModelWrapper>();
return scoped_ptr<DataModelWrapper>(
new FullWalletShippingWrapper(full_wallet));
}
NOTREACHED();
return scoped_ptr<DataModelWrapper>();
}
void FillOutputForSectionWithComparator(
DialogSection section, const DetailInputs& inputs,
const InputFieldComparator& compare,
FormStructure& form_structure, wallet::FullWallet* full_wallet,
const base::string16& email_address) {
scoped_ptr<DataModelWrapper> wrapper = CreateWrapper(section, full_wallet);
if (wrapper)
wrapper->FillFormStructure(inputs, compare, &form_structure);
}
void FillOutputForSection(
DialogSection section,
FormStructure& form_structure,
wallet::FullWallet* full_wallet,
const base::string16& email_address) {
DetailInputs inputs;
common::BuildInputsForSection(section, &inputs);
FillOutputForSectionWithComparator(
section, inputs,
base::Bind(common::DetailInputMatchesField, section),
form_structure, full_wallet, email_address);
if (section == SECTION_CC_BILLING) {
// Email is hidden while using Wallet, special case it.
for (size_t i = 0; i < form_structure.field_count(); ++i) {
AutofillField* field = form_structure.field(i);
if (field->Type().GetStorableType() == EMAIL_ADDRESS)
field->value = email_address;
}
}
}
// Returns true if |input_type| in |section| is needed for |form_structure|.
bool IsSectionInputUsedInFormStructure(DialogSection section,
ServerFieldType input_type,
const FormStructure& form_structure) {
const DetailInput input = { 0, input_type };
for (size_t i = 0; i < form_structure.field_count(); ++i) {
const AutofillField* field = form_structure.field(i);
if (field && common::DetailInputMatchesField(section, input, *field))
return true;
}
return false;
}
// Returns true if one of |inputs| in |section| is needed for |form_structure|.
bool IsSectionInputsUsedInFormStructure(DialogSection section,
const ServerFieldType* input_types,
const size_t input_types_size,
const FormStructure& form_structure) {
for (size_t i = 0; i < input_types_size; ++i) {
if (IsSectionInputUsedInFormStructure(
section, input_types[i], form_structure)) {
return true;
}
}
return false;
}
} // namespace
// static
base::WeakPtr<AutofillDialogController> AutofillDialogControllerAndroid::Create(
content::WebContents* contents,
const FormData& form_structure,
const GURL& source_url,
const base::Callback<void(const FormStructure*)>& callback) {
// AutofillDialogControllerAndroid owns itself.
AutofillDialogControllerAndroid* autofill_dialog_controller =
new AutofillDialogControllerAndroid(contents,
form_structure,
source_url,
callback);
return autofill_dialog_controller->weak_ptr_factory_.GetWeakPtr();
}
// static
void AutofillDialogControllerAndroid::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterDictionaryPref(
::prefs::kAutofillDialogDefaults,
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
}
#if defined(ENABLE_AUTOFILL_DIALOG)
// static
base::WeakPtr<AutofillDialogController>
AutofillDialogController::Create(
content::WebContents* contents,
const FormData& form_structure,
const GURL& source_url,
const base::Callback<void(const FormStructure*)>& callback) {
return AutofillDialogControllerAndroid::Create(contents,
form_structure,
source_url,
callback);
}
// static
void AutofillDialogController::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
AutofillDialogControllerAndroid::RegisterProfilePrefs(registry);
}
#endif // defined(ENABLE_AUTOFILL_DIALOG)
AutofillDialogControllerAndroid::~AutofillDialogControllerAndroid() {
if (java_object_.is_null())
return;
JNIEnv* env = base::android::AttachCurrentThread();
Java_AutofillDialogControllerAndroid_onDestroy(env, java_object_.obj());
}
void AutofillDialogControllerAndroid::Show() {
JNIEnv* env = base::android::AttachCurrentThread();
dialog_shown_timestamp_ = base::Time::Now();
content::NavigationEntry* entry = contents_->GetController().GetActiveEntry();
const GURL& active_url = entry ? entry->GetURL() : contents_->GetURL();
invoked_from_same_origin_ = active_url.GetOrigin() == source_url_.GetOrigin();
// Determine what field types should be included in the dialog.
bool has_types = false;
bool has_sections = false;
form_structure_.ParseFieldTypesFromAutocompleteAttributes(
&has_types, &has_sections);
// Fail if the author didn't specify autocomplete types, or
// if the dialog shouldn't be shown in a given circumstances.
if (!has_types ||
!Java_AutofillDialogControllerAndroid_isDialogAllowed(
env,
RequestingCreditCardInfo(),
TransmissionWillBeSecure(),
invoked_from_same_origin_)) {
callback_.Run(NULL);
delete this;
return;
}
// Log any relevant UI metrics and security exceptions.
GetMetricLogger().LogDialogUiEvent(AutofillMetrics::DIALOG_UI_SHOWN);
GetMetricLogger().LogDialogSecurityMetric(
AutofillMetrics::SECURITY_METRIC_DIALOG_SHOWN);
if (RequestingCreditCardInfo() && !TransmissionWillBeSecure()) {
GetMetricLogger().LogDialogSecurityMetric(
AutofillMetrics::SECURITY_METRIC_CREDIT_CARD_OVER_HTTP);
}
if (!invoked_from_same_origin_) {
GetMetricLogger().LogDialogSecurityMetric(
AutofillMetrics::SECURITY_METRIC_CROSS_ORIGIN_FRAME);
}
const ServerFieldType full_billing_is_necessary_if[] = {
ADDRESS_BILLING_LINE1,
ADDRESS_BILLING_LINE2,
ADDRESS_BILLING_CITY,
ADDRESS_BILLING_STATE,
PHONE_BILLING_WHOLE_NUMBER
};
const bool request_full_billing_address =
IsSectionInputsUsedInFormStructure(
SECTION_BILLING,
full_billing_is_necessary_if,
arraysize(full_billing_is_necessary_if),
form_structure_);
const bool request_phone_numbers =
IsSectionInputUsedInFormStructure(
SECTION_BILLING,
PHONE_BILLING_WHOLE_NUMBER,
form_structure_) ||
IsSectionInputUsedInFormStructure(
SECTION_SHIPPING,
PHONE_HOME_WHOLE_NUMBER,
form_structure_);
bool request_shipping_address = false;
{
DetailInputs inputs;
common::BuildInputsForSection(SECTION_SHIPPING, &inputs);
EmptyDataModelWrapper empty_wrapper;
request_shipping_address = empty_wrapper.FillFormStructure(
inputs,
base::Bind(common::DetailInputMatchesField, SECTION_SHIPPING),
&form_structure_);
}
const bool incognito_mode = profile_->IsOffTheRecord();
bool last_used_choice_is_autofill = false;
base::string16 last_used_account_name;
std::string last_used_billing;
std::string last_used_shipping;
std::string last_used_credit_card;
{
const base::DictionaryValue* defaults =
profile_->GetPrefs()->GetDictionary(::prefs::kAutofillDialogDefaults);
if (defaults) {
defaults->GetString(kLastUsedAccountName, &last_used_account_name);
defaults->GetBoolean(kLastUsedChoiceIsAutofill,
&last_used_choice_is_autofill);
defaults->GetString(kLastUsedBillingAddressGuid, &last_used_billing);
defaults->GetString(kLastUsedShippingAddressGuid, &last_used_shipping);
defaults->GetString(kLastUsedCreditCardGuid, &last_used_credit_card);
} else {
DLOG(ERROR) << "Failed to read AutofillDialog preferences";
}
}
if (contents_->GetBrowserContext()->IsOffTheRecord())
last_used_choice_is_autofill = true;
ScopedJavaLocalRef<jstring> jlast_used_account_name =
base::android::ConvertUTF16ToJavaString(
env, last_used_account_name);
ScopedJavaLocalRef<jstring> jlast_used_billing =
base::android::ConvertUTF8ToJavaString(
env, last_used_billing);
ScopedJavaLocalRef<jstring> jlast_used_shipping =
base::android::ConvertUTF8ToJavaString(
env, last_used_shipping);
ScopedJavaLocalRef<jstring> jlast_used_card =
base::android::ConvertUTF8ToJavaString(
env, last_used_credit_card);
ScopedJavaLocalRef<jstring> jmerchant_domain =
base::android::ConvertUTF8ToJavaString(
env, source_url_.GetOrigin().spec());
java_object_.Reset(Java_AutofillDialogControllerAndroid_create(
env,
reinterpret_cast<jint>(this),
WindowAndroidHelper::FromWebContents(contents_)->
GetWindowAndroid()->GetJavaObject().obj(),
request_full_billing_address, request_shipping_address,
request_phone_numbers, incognito_mode,
last_used_choice_is_autofill, jlast_used_account_name.obj(),
jlast_used_billing.obj(), jlast_used_shipping.obj(),
jlast_used_card.obj(),
jmerchant_domain.obj()));
}
void AutofillDialogControllerAndroid::Hide() {
// TODO(aruslan): http://crbug.com/177373 Autocheckout.
NOTIMPLEMENTED();
}
void AutofillDialogControllerAndroid::TabActivated() {}
// static
bool AutofillDialogControllerAndroid::
RegisterAutofillDialogControllerAndroid(JNIEnv* env) {
return RegisterNativesImpl(env);
}
void AutofillDialogControllerAndroid::DialogCancel(JNIEnv* env,
jobject obj) {
LogOnCancelMetrics();
callback_.Run(NULL);
}
void AutofillDialogControllerAndroid::DialogContinue(
JNIEnv* env,
jobject obj,
jobject wallet,
jboolean jlast_used_choice_is_autofill,
jstring jlast_used_account_name,
jstring jlast_used_billing,
jstring jlast_used_shipping,
jstring jlast_used_card) {
const string16 email = AutofillDialogResult::GetWalletEmail(env, wallet);
const std::string google_transaction_id =
AutofillDialogResult::GetWalletGoogleTransactionId(env, wallet);
const string16 last_used_account_name =
base::android::ConvertJavaStringToUTF16(env, jlast_used_account_name);
const std::string last_used_billing =
base::android::ConvertJavaStringToUTF8(env, jlast_used_billing);
const std::string last_used_shipping =
base::android::ConvertJavaStringToUTF8(env, jlast_used_shipping);
const std::string last_used_card =
base::android::ConvertJavaStringToUTF8(env, jlast_used_card);
scoped_ptr<wallet::FullWallet> full_wallet =
AutofillDialogResult::ConvertFromJava(env, wallet);
FillOutputForSection(
SECTION_CC_BILLING, form_structure_, full_wallet.get(), email);
FillOutputForSection(
SECTION_SHIPPING, form_structure_, full_wallet.get(), email);
{
DictionaryPrefUpdate updater(profile_->GetPrefs(),
::prefs::kAutofillDialogDefaults);
base::DictionaryValue* defaults = updater.Get();
if (defaults) {
const bool last_used_choice_is_autofill = !!jlast_used_choice_is_autofill;
defaults->SetString(kLastUsedAccountName, last_used_account_name);
defaults->SetBoolean(kLastUsedChoiceIsAutofill,
last_used_choice_is_autofill);
if (!last_used_billing.empty())
defaults->SetString(kLastUsedBillingAddressGuid, last_used_billing);
if (!last_used_shipping.empty())
defaults->SetString(kLastUsedShippingAddressGuid, last_used_shipping);
if (!last_used_card.empty())
defaults->SetString(kLastUsedCreditCardGuid, last_used_card);
} else {
LOG(ERROR) << "Failed to save AutofillDialog preferences";
}
}
LogOnFinishSubmitMetrics();
// Callback should be called as late as possible.
callback_.Run(&form_structure_);
// This might delete us.
Hide();
}
AutofillDialogControllerAndroid::AutofillDialogControllerAndroid(
content::WebContents* contents,
const FormData& form_structure,
const GURL& source_url,
const base::Callback<void(const FormStructure*)>& callback)
: profile_(Profile::FromBrowserContext(contents->GetBrowserContext())),
contents_(contents),
initial_user_state_(AutofillMetrics::DIALOG_USER_STATE_UNKNOWN),
form_structure_(form_structure),
invoked_from_same_origin_(true),
source_url_(source_url),
callback_(callback),
cares_about_shipping_(true),
weak_ptr_factory_(this),
was_ui_latency_logged_(false) {
DCHECK(!callback_.is_null());
}
bool AutofillDialogControllerAndroid::RequestingCreditCardInfo() const {
DCHECK_GT(form_structure_.field_count(), 0U);
for (size_t i = 0; i < form_structure_.field_count(); ++i) {
AutofillType type = form_structure_.field(i)->Type();
if (common::IsCreditCardType(type.GetStorableType()))
return true;
}
return false;
}
bool AutofillDialogControllerAndroid::TransmissionWillBeSecure() const {
return source_url_.SchemeIs(content::kHttpsScheme);
}
void AutofillDialogControllerAndroid::LogOnFinishSubmitMetrics() {
GetMetricLogger().LogDialogUiDuration(
base::Time::Now() - dialog_shown_timestamp_,
AutofillMetrics::DIALOG_ACCEPTED);
GetMetricLogger().LogDialogUiEvent(AutofillMetrics::DIALOG_UI_ACCEPTED);
}
void AutofillDialogControllerAndroid::LogOnCancelMetrics() {
GetMetricLogger().LogDialogUiDuration(
base::Time::Now() - dialog_shown_timestamp_,
AutofillMetrics::DIALOG_CANCELED);
GetMetricLogger().LogDialogUiEvent(AutofillMetrics::DIALOG_UI_CANCELED);
}
} // namespace autofill