Skip to content

Commit

Permalink
[Leipzig][Android] Support required address fields in the AddressEditor
Browse files Browse the repository at this point in the history
This CL updates the Autofill Settings address editor to support
required fields. Fields are only marked required when the address
is stored in the account to prepare users for migration from
the deprecated local/sync storage to the account.

Bug: 1408423
Change-Id: Iece698b6270bd7124ac6442a3e81e79c5381c3ea
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4221248
Reviewed-by: Stephen McGruer <smcgruer@chromium.org>
Reviewed-by: Florian Leimgruber <fleimgruber@google.com>
Commit-Queue: Dmitry Vykochko <vykochko@google.com>
Cr-Commit-Position: refs/heads/main@{#1102697}
  • Loading branch information
DVykochko authored and Chromium LUCI CQ committed Feb 8, 2023
1 parent fe48fc7 commit 260be64
Show file tree
Hide file tree
Showing 15 changed files with 186 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.chromium.chrome.browser.autofill.PersonalDataManager;
import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
import org.chromium.chrome.browser.autofill.PhoneNumberUtil;
import org.chromium.chrome.browser.autofill.Source;
import org.chromium.chrome.browser.autofill.prefeditor.EditorBase;
import org.chromium.chrome.browser.autofill.prefeditor.EditorModel;
import org.chromium.chrome.browser.autofill.settings.AutofillProfileBridge.AddressField;
Expand Down Expand Up @@ -130,11 +131,12 @@ public void edit(@Nullable final AutofillAddress toEdit,

if (mAutofillProfileBridge == null) mAutofillProfileBridge = new AutofillProfileBridge();

// If |toEdit| is null, we're creating a new autofill profile with the country code of the
// default locale on this device.
final String editTitle;
final AutofillAddress address;
if (toEdit == null) {
final boolean isProfileNew = toEdit == null;
if (isProfileNew) {
// When creating a new autofill profile, we use the country code of the default locale
// on the device.
address = new AutofillAddress(
mContext, new AutofillProfile(), CompletenessCheckType.NORMAL);
editTitle = mContext.getString(R.string.autofill_create_profile);
Expand Down Expand Up @@ -167,7 +169,8 @@ public void onResult(Pair<String, Runnable> eventData) {
mEditor.removeAllFields();
mPhoneFormatter.setCountryCode(eventData.first);
mPhoneValidator.setCountryCode(eventData.first);
addAddressFieldsToEditor(eventData.first, Locale.getDefault().getLanguage());
addAddressFieldsToEditor(
eventData.first, Locale.getDefault().getLanguage(), isProfileNew);
// Notify EditorDialog that the fields in the model have changed. EditorDialog
// should re-read the model and update the UI accordingly.
mHandler.post(eventData.second);
Expand Down Expand Up @@ -276,7 +279,8 @@ public void onResult(Pair<String, Runnable> eventData) {

// This should be called when all required fields are put in mAddressField.
setAddressFieldValuesFromCache();
addAddressFieldsToEditor(mCountryField.getValue().toString(), mProfile.getLanguageCode());
addAddressFieldsToEditor(
mCountryField.getValue().toString(), mProfile.getLanguageCode(), isProfileNew);
mEditorDialog.show(mEditor);
}

Expand Down Expand Up @@ -384,12 +388,20 @@ private void setAddressFieldValuesFromCache() {
* For example, "US" will not add dependent locality to the editor. A "JP" address will start
* with a person's full name or with a prefecture name, depending on whether the language code
* is "ja-Latn" or "ja".
*
* @param countryCode The country for which fields are to be added.
* @param languageCode The language in which localized strings (e.g. label) are presented.
* @param isProfileNew Whether the profile new or not is required for setting validation:
* it is softer for existing profiles with originally invalid values.
*/
private void addAddressFieldsToEditor(String countryCode, String languageCode) {
mAddressUiComponents =
mAutofillProfileBridge.getAddressUiComponents(countryCode, languageCode);
private void addAddressFieldsToEditor(
String countryCode, String languageCode, boolean isProfileNew) {
mAddressUiComponents = mAutofillProfileBridge.getAddressUiComponents(
countryCode, languageCode, AddressValidationType.ACCOUNT);
// In terms of order, country must be the first field.
mEditor.addField(mCountryField);

boolean isStoredInAccount = mProfile.getSource() == Source.ACCOUNT;
for (int i = 0; i < mAddressUiComponents.size(); i++) {
AddressUiComponent component = mAddressUiComponents.get(i);

Expand All @@ -406,6 +418,18 @@ private void addAddressFieldsToEditor(String countryCode, String languageCode) {
field.setIsFullLine(component.isFullLine || component.id == AddressField.LOCALITY
|| component.id == AddressField.DEPENDENT_LOCALITY);

// For account-stored profiles, we enforce that required fields are non-empty. This
// applies to all fields for a new profile, and previously non-empty fields for an
// existing profile.
String fieldContents = AutofillAddress.getProfileField(mProfile, component.id);
if (isStoredInAccount && component.isRequired
&& (isProfileNew || !TextUtils.isEmpty(fieldContents))) {
String message =
mContext.getString(R.string.autofill_edit_address_required_field_error)
.replace("$1", component.label);
field.setRequiredErrorMessage(message);
}

mEditor.addField(field);
}
// Phone number (and email/nickname if applicable) are the last fields of the address.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,20 +166,21 @@ public AddressUiComponent(int id, String label, boolean isRequired, boolean isFu
* @param languageCode The language code associated with the saved autofill profile that ui
* components are being retrieved for; can be null if ui components are
* being retrieved for a new profile.
* @param validation The target usage validation rules.
* @return A list of address UI components. The ordering in the list specifies the order these
* components should appear in the UI.
*/
public List<AddressUiComponent> getAddressUiComponents(
String countryCode, String languageCode) {
String countryCode, String languageCode, @AddressValidationType int validationType) {
List<Integer> componentIds = new ArrayList<>();
List<String> componentNames = new ArrayList<>();
List<Integer> componentRequired = new ArrayList<>();
List<Integer> componentLengths = new ArrayList<>();
List<AddressUiComponent> uiComponents = new ArrayList<>();

mCurrentBestLanguageCode =
AutofillProfileBridgeJni.get().getAddressUiComponents(countryCode, languageCode,
componentIds, componentNames, componentRequired, componentLengths);
mCurrentBestLanguageCode = AutofillProfileBridgeJni.get().getAddressUiComponents(
countryCode, languageCode, validationType, componentIds, componentNames,
componentRequired, componentLengths);

for (int i = 0; i < componentIds.size(); i++) {
uiComponents.add(new AddressUiComponent(componentIds.get(i), componentNames.get(i),
Expand Down Expand Up @@ -237,7 +238,8 @@ interface Natives {
void getSupportedCountries(List<String> countryCodes, List<String> countryNames);
void getRequiredFields(String countryCode, List<Integer> requiredFields);
String getAddressUiComponents(String countryCode, String languageCode,
List<Integer> componentIds, List<String> componentNames,
List<Integer> componentRequired, List<Integer> componentLengths);
@AddressValidationType int validationType, List<Integer> componentIds,
List<String> componentNames, List<Integer> componentRequired,
List<Integer> componentLengths);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.chromium.chrome.browser.autofill.PhoneNumberUtil;
import org.chromium.chrome.browser.autofill.prefeditor.EditorBase;
import org.chromium.chrome.browser.autofill.prefeditor.EditorModel;
import org.chromium.chrome.browser.autofill.settings.AddressValidationType;
import org.chromium.chrome.browser.autofill.settings.AutofillProfileBridge;
import org.chromium.chrome.browser.autofill.settings.AutofillProfileBridge.AddressField;
import org.chromium.chrome.browser.autofill.settings.AutofillProfileBridge.AddressUiComponent;
Expand Down Expand Up @@ -526,8 +527,8 @@ private void loadAdminAreasForCountry(String countryCode) {
* the profile that's being edited.
*/
private void addAddressFieldsToEditor(String countryCode, String languageCode) {
mAddressUiComponents =
mAutofillProfileBridge.getAddressUiComponents(countryCode, languageCode);
mAddressUiComponents = mAutofillProfileBridge.getAddressUiComponents(
countryCode, languageCode, AddressValidationType.PAYMENT_REQUEST);
// In terms of order, country must be the first field.
mCountryField.setCustomErrorMessage(getAddressError(AddressField.COUNTRY));
mEditor.addField(mCountryField);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.chromium.chrome.R;
import org.chromium.chrome.browser.autofill.AutofillTestHelper;
import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
import org.chromium.chrome.browser.autofill.Source;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.settings.SettingsActivity;
import org.chromium.chrome.browser.settings.SettingsActivityTestRule;
Expand Down Expand Up @@ -218,6 +219,79 @@ public void testEditProfile() throws Exception {
Assert.assertNull(oldProfile);
}

@Test
@MediumTest
@Feature({"Preferences"})
@Features.EnableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_SUPPORT_FOR_HONORIFIC_PREFIXES})
public void testEditAccountProfile() throws Exception {
mHelper.setProfile(new AutofillProfile("", "https://example.com", true, Source.ACCOUNT,
"" /* honorific prefix */, "Account Updated #0", "Google", "111 Fourth St",
"California", "Los Angeles", "", "90291", "", "US", "650-253-0000",
"fourth@gmail.com", "en-US"));

AutofillProfilesFragment autofillProfileFragment = sSettingsActivityTestRule.getFragment();

// Check the preferences on the initial screen.
Assert.assertEquals(7 /* One toggle + one add button + 5 profiles. */,
autofillProfileFragment.getPreferenceScreen().getPreferenceCount());
AutofillProfileEditorPreference johnProfile =
autofillProfileFragment.findPreference("Account Updated #0");
Assert.assertNotNull(johnProfile);

// Invalid input.
updatePreferencesAndWait(autofillProfileFragment, johnProfile,
new String[] {"Dr.", "Account Updated #1", "Google",
"" /* Street address is required. */, "Los Angeles", "CA", "90291",
"650-253-0000", "edit@profile.com"},
R.id.editor_dialog_done_button, true);

// Fix invalid input.
updatePreferencesAndWait(autofillProfileFragment, johnProfile,
new String[] {"Dr.", "Account Updated #2", "Google",
"222 Fourth St" /* Enter street address. */, "Los Angeles", "CA", "90291",
"650-253-0000", "edit@profile.com"},
R.id.editor_dialog_done_button, false);
// Check if the preferences are updated correctly.
Assert.assertEquals(7 /* One toggle + one add button + five profiles. */,
autofillProfileFragment.getPreferenceScreen().getPreferenceCount());
AutofillProfileEditorPreference editedProfile =
autofillProfileFragment.findPreference("Account Updated #2");
Assert.assertNotNull(editedProfile);
}

@Test
@MediumTest
@Feature({"Preferences"})
@Features.EnableFeatures({ChromeFeatureList.AUTOFILL_ENABLE_SUPPORT_FOR_HONORIFIC_PREFIXES})
public void testEditInvalidAccountProfile() throws Exception {
mHelper.setProfile(new AutofillProfile("", "https://example.com", true, Source.ACCOUNT,
"" /* honorific prefix */, "Account Updated #0", "Google",
"" /** Street address is required in US but already missing. */, "California",
"Los Angeles", "", "90291", "", "US", "650-253-0000", "fourth@gmail.com", "en-US"));

AutofillProfilesFragment autofillProfileFragment = sSettingsActivityTestRule.getFragment();

// Check the preferences on the initial screen.
Assert.assertEquals(7 /* One toggle + one add button + 5 profiles. */,
autofillProfileFragment.getPreferenceScreen().getPreferenceCount());
AutofillProfileEditorPreference johnProfile =
autofillProfileFragment.findPreference("Account Updated #0");
Assert.assertNotNull(johnProfile);

// Edit profile.
updatePreferencesAndWait(autofillProfileFragment, johnProfile,
new String[] {"Dr.", "Account Updated #1", "Google",
"" /* Dont fix missing Street address. */, "Los Angeles", "CA", "90291",
"650-253-0000", "edit@profile.com"},
R.id.editor_dialog_done_button, false);
// Check if the preferences are updated correctly.
Assert.assertEquals(7 /* One toggle + one add button + five profiles. */,
autofillProfileFragment.getPreferenceScreen().getPreferenceCount());
AutofillProfileEditorPreference editedProfile =
autofillProfileFragment.findPreference("Account Updated #1");
Assert.assertNotNull(editedProfile);
}

@Test
@MediumTest
@Feature({"Preferences"})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#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/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/android/chrome_jni_headers/AutofillProfileBridge_jni.h"
Expand Down Expand Up @@ -95,6 +96,7 @@ JNI_AutofillProfileBridge_GetAddressUiComponents(
JNIEnv* env,
const JavaParamRef<jstring>& j_country_code,
const JavaParamRef<jstring>& j_language_code,
jint j_validation_type,
const JavaParamRef<jobject>& j_id_list,
const JavaParamRef<jobject>& j_name_list,
const JavaParamRef<jobject>& j_required_list,
Expand Down Expand Up @@ -125,13 +127,23 @@ JNI_AutofillProfileBridge_GetAddressUiComponents(
ExtendAddressComponents(ui_components, country, localization,
/*include_literals=*/false);

AddressValidationType validation_type =
static_cast<AddressValidationType>(j_validation_type);
for (const auto& ui_component : ui_components) {
component_ids.push_back(ui_component.field);
component_labels.push_back(ui_component.name);
component_required.push_back(
IsFieldRequired(ui_component.field, country_code));
component_length.push_back(ui_component.length_hint ==
AddressUiComponent::HINT_LONG);
component_ids.push_back(ui_component.field);

switch (validation_type) {
case AddressValidationType::kPaymentRequest:
component_required.push_back(
IsFieldRequired(ui_component.field, country_code));
break;
case AddressValidationType::kAccount:
component_required.push_back(
country.IsAddressFieldRequired(ui_component.field));
}
}

Java_AutofillProfileBridge_intArrayToList(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ class WebContents;

namespace autofill {

// Specifies which rules are to be used for address validation.
// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chrome.browser.autofill.settings
enum class AddressValidationType {
// Validation rules used for the PaymentRequest API (e.g. for billing
// addresses).
kPaymentRequest = 0,
// Validation rules used for addresses stored in the user account.
kAccount = 1
};

// Opens the autofill settings page for profiles.
void ShowAutofillProfileSettings(content::WebContents* web_contents);

Expand Down
1 change: 1 addition & 0 deletions chrome/browser/autofill/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import("//build/config/android/rules.gni")

java_cpp_enum("autofill_generated_enums") {
sources = [
"//chrome/browser/android/preferences/autofill/autofill_profile_bridge.h",
"//components/autofill/core/browser/data_model/autofill_profile.h",
"//components/autofill/core/browser/data_model/autofill_structured_address_component.h",
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ public String getOrigin() {
return mOrigin;
}

@CalledByNative("AutofillProfile")
public @Source int getSource() {
return mSource;
}
Expand Down

0 comments on commit 260be64

Please sign in to comment.