Index: chrome/android/java/src/org/chromium/chrome/browser/payments/AddressEditor.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/AddressEditor.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/AddressEditor.java |
index 4fc0ae9c74a86d77f876bef5582a8b69a0faa623..ab0e67bb4edd65b3ce3814d98d0d97264be43ef0 100644 |
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/AddressEditor.java |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/AddressEditor.java |
@@ -4,6 +4,7 @@ |
package org.chromium.chrome.browser.payments; |
+import android.app.ProgressDialog; |
import android.os.Handler; |
import android.util.Pair; |
@@ -11,6 +12,7 @@ import org.chromium.base.Callback; |
import org.chromium.chrome.R; |
import org.chromium.chrome.browser.autofill.PersonalDataManager; |
import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile; |
+import org.chromium.chrome.browser.autofill.PersonalDataManager.GetSubKeysRequestDelegate; |
import org.chromium.chrome.browser.autofill.PhoneNumberUtil; |
import org.chromium.chrome.browser.payments.ui.EditorFieldModel; |
import org.chromium.chrome.browser.payments.ui.EditorFieldModel.EditorFieldValidator; |
@@ -31,15 +33,27 @@ import javax.annotation.Nullable; |
/** |
* An address editor. Can be used for either shipping or billing address editing. |
*/ |
-public class AddressEditor extends EditorBase<AutofillAddress> { |
+public class AddressEditor |
+ extends EditorBase<AutofillAddress> implements GetSubKeysRequestDelegate { |
private final Handler mHandler = new Handler(); |
private final Map<Integer, EditorFieldModel> mAddressFields = new HashMap<>(); |
private final Set<CharSequence> mPhoneNumbers = new HashSet<>(); |
- @Nullable private AutofillProfileBridge mAutofillProfileBridge; |
- @Nullable private EditorFieldModel mCountryField; |
- @Nullable private EditorFieldModel mPhoneField; |
- @Nullable private EditorFieldValidator mPhoneValidator; |
- @Nullable private List<AddressUiComponent> mAddressUiComponents; |
+ @Nullable |
+ private AutofillProfileBridge mAutofillProfileBridge; |
+ @Nullable |
+ private EditorFieldModel mCountryField; |
+ @Nullable |
+ private EditorFieldModel mPhoneField; |
+ @Nullable |
+ private EditorFieldValidator mPhoneValidator; |
+ @Nullable |
+ private List<AddressUiComponent> mAddressUiComponents; |
+ private boolean mAdminAreasLoaded; |
+ private String mRecentlySelectedCountry; |
+ private Runnable mCountryChangeCallback; |
+ private AutofillProfile mProfile; |
+ private EditorModel mEditor; |
+ private ProgressDialog mProgressDialog; |
/** |
* Adds the given phone number to the autocomplete set, if it's valid. |
@@ -72,17 +86,20 @@ public class AddressEditor extends EditorBase<AutofillAddress> { |
// default locale on this device. |
boolean isNewAddress = toEdit == null; |
- // Ensure that |address| and |profile| are always not null. |
+ // Ensure that |address| and |mProfile| are always not null. |
final AutofillAddress address = |
isNewAddress ? new AutofillAddress(mContext, new AutofillProfile()) : toEdit; |
- final AutofillProfile profile = address.getProfile(); |
+ mProfile = address.getProfile(); |
// The title of the editor depends on whether we're adding a new address or editing an |
// existing address. |
- final EditorModel editor = |
- new EditorModel(isNewAddress |
- ? mContext.getString(R.string.autofill_create_profile) |
- : toEdit.getEditTitle()); |
+ mEditor = |
+ new EditorModel(isNewAddress ? mContext.getString(R.string.autofill_create_profile) |
+ : toEdit.getEditTitle()); |
+ |
+ // When edit is called, a new form is started, so the country on the |
+ // dropdown list is not changed. => mRecentlySelectedCountry should be null. |
+ mRecentlySelectedCountry = null; |
// The country dropdown is always present on the editor. |
if (mCountryField == null) { |
@@ -96,23 +113,24 @@ public class AddressEditor extends EditorBase<AutofillAddress> { |
// discarded, so their contents are preserved. |
mCountryField.setDropdownCallback(new Callback<Pair<String, Runnable>>() { |
@Override |
+ /** |
+ * If the selected country on the country dropdown list is changed, |
+ * the first element of eventData is the recently selected dropdown key, |
+ * the second element is the callback to invoke for when the dropdown |
+ * change has been processed. |
+ */ |
public void onResult(Pair<String, Runnable> eventData) { |
- editor.removeAllFields(); |
- editor.addField(mCountryField); |
- addAddressTextFieldsToEditor(editor, eventData.first, |
- Locale.getDefault().getLanguage()); |
- editor.addField(mPhoneField); |
- |
- // Notify EditorView that the fields in the model have changed. EditorView should |
- // re-read the model and update the UI accordingly. |
- mHandler.post(eventData.second); |
+ mEditor.removeAllFields(); |
+ showProgressDialog(); |
+ mRecentlySelectedCountry = eventData.first; |
+ mCountryChangeCallback = eventData.second; |
+ loadAdminAreasForCountry(mRecentlySelectedCountry); |
} |
}); |
// Country dropdown is cached, so the selected item needs to be updated for the new profile |
// that's being edited. This will not fire the dropdown callback. |
- mCountryField.setValue(AutofillAddress.getCountryCode(profile)); |
- editor.addField(mCountryField); |
+ mCountryField.setValue(AutofillAddress.getCountryCode(mProfile)); |
// There's a finite number of fields for address editing. Changing the country will re-order |
// and relabel the fields. The meaning of each field remains the same. |
@@ -122,10 +140,6 @@ public class AddressEditor extends EditorBase<AutofillAddress> { |
mAddressFields.put(AddressField.DEPENDENT_LOCALITY, EditorFieldModel.createTextInput()); |
mAddressFields.put(AddressField.ORGANIZATION, EditorFieldModel.createTextInput()); |
- // State should be formatted in all capitals. |
- mAddressFields.put(AddressField.ADMIN_AREA, EditorFieldModel.createTextInput( |
- EditorFieldModel.INPUT_TYPE_HINT_REGION)); |
- |
// Sorting code and postal code (a.k.a. ZIP code) should show both letters and digits on |
// the keyboard, if possible. |
mAddressFields.put(AddressField.SORTING_CODE, EditorFieldModel.createTextInput( |
@@ -142,18 +156,6 @@ public class AddressEditor extends EditorBase<AutofillAddress> { |
EditorFieldModel.INPUT_TYPE_HINT_PERSON_NAME)); |
} |
- // Address fields are cached, so their values need to be updated for every new profile |
- // that's being edited. |
- for (Map.Entry<Integer, EditorFieldModel> entry : mAddressFields.entrySet()) { |
- entry.getValue().setValue(AutofillAddress.getProfileField(profile, entry.getKey())); |
- } |
- |
- // Both country code and language code dictate which fields should be added to the editor. |
- // For example, "US" will not add dependent locality to the editor. A "JP" address will |
- // start with a person's full name or a with a prefecture name, depending on whether the |
- // language code is "ja-Latn" or "ja". |
- addAddressTextFieldsToEditor( |
- editor, mCountryField.getValue().toString(), profile.getLanguageCode()); |
// Phone number is present and required for all countries. |
if (mPhoneField == null) { |
@@ -166,30 +168,48 @@ public class AddressEditor extends EditorBase<AutofillAddress> { |
// Phone number field is cached, so its value needs to be updated for every new profile |
// that's being edited. |
- mPhoneField.setValue(profile.getPhoneNumber()); |
- editor.addField(mPhoneField); |
+ mPhoneField.setValue(mProfile.getPhoneNumber()); |
// If the user clicks [Cancel], send |toEdit| address back to the caller, which was the |
// original state (could be null, a complete address, a partial address). |
- editor.setCancelCallback(new Runnable() { |
+ mEditor.setCancelCallback(new Runnable() { |
@Override |
public void run() { |
+ // This makes sure that onSubKeysReceived returns early if it's |
+ // ever called when Cancel has already occurred. |
+ mAdminAreasLoaded = true; |
+ PersonalDataManager.getInstance().cancelPendingGetSubKeys(); |
callback.onResult(toEdit); |
} |
}); |
// If the user clicks [Done], save changes on disk, mark the address "complete," and send it |
// back to the caller. |
- editor.setDoneCallback(new Runnable() { |
+ mEditor.setDoneCallback(new Runnable() { |
@Override |
public void run() { |
- commitChanges(profile); |
- address.completeAddress(profile); |
+ mAdminAreasLoaded = true; |
+ PersonalDataManager.getInstance().cancelPendingGetSubKeys(); |
+ commitChanges(mProfile); |
+ address.completeAddress(mProfile); |
callback.onResult(address); |
} |
}); |
- mEditorView.show(editor); |
+ loadAdminAreasForCountry(mProfile.getCountryCode()); |
+ } |
+ |
+ private void showProgressDialog() { |
+ mProgressDialog = new ProgressDialog(mContext); |
+ mProgressDialog.setMessage(mContext.getText(R.string.payments_loading_message)); |
+ mProgressDialog.show(); |
+ } |
+ |
+ private void dismissProgressDialog() { |
+ if (mProgressDialog != null && mProgressDialog.isShowing()) { |
+ mProgressDialog.dismiss(); |
+ } |
+ mProgressDialog = null; |
} |
/** Saves the edited profile on disk. */ |
@@ -221,7 +241,7 @@ public class AddressEditor extends EditorBase<AutofillAddress> { |
} |
// Save the edited autofill profile locally. |
- profile.setGUID(PersonalDataManager.getInstance().setProfileToLocal(profile)); |
+ profile.setGUID(PersonalDataManager.getInstance().setProfileToLocal(mProfile)); |
profile.setIsLocal(true); |
} |
@@ -266,27 +286,109 @@ public class AddressEditor extends EditorBase<AutofillAddress> { |
return value == null ? "" : value.toString(); |
} |
+ private void setAddressFieldValuesFromCache() { |
+ // Address fields are cached, so their values need to be updated for every new profile |
+ // that's being edited. |
+ for (Map.Entry<Integer, EditorFieldModel> entry : mAddressFields.entrySet()) { |
+ entry.getValue().setValue(AutofillAddress.getProfileField(mProfile, entry.getKey())); |
+ } |
+ } |
+ |
+ @Override |
+ public void onSubKeysReceived(String[] adminAreas) { |
+ if (mAdminAreasLoaded) return; |
+ mAdminAreasLoaded = true; |
+ |
+ // If Chrome can't get admin areas from the server or there is no admin area on the server, |
+ // then use the text field. |
+ // Otherwise, use the dropdown list. |
+ if (adminAreas == null || adminAreas.length == 0) { |
+ mAddressFields.put(AddressField.ADMIN_AREA, EditorFieldModel.createTextInput()); |
+ } else { |
+ mAddressFields.put(AddressField.ADMIN_AREA, EditorFieldModel.createDropdown()); |
+ } |
+ |
+ // Admin areas need to be fetched in two cases: |
+ // 1. Initial loading of the form. |
+ // 2. When the selected country is changed in the form. |
+ // mRecentlySelectedCountry is not null if and only if it's the second case |
+ if (mRecentlySelectedCountry != null) { |
+ dismissProgressDialog(); |
+ // Both country code and language code dictate which fields should be added to the |
+ // editor. |
+ // For example, "US" will not add dependent locality to the editor. A "JP" address will |
+ // start with a person's full name or a with a prefecture name, depending on whether the |
+ // language code is "ja-Latn" or "ja". |
+ addAddressFieldsToEditor( |
+ mRecentlySelectedCountry, Locale.getDefault().getLanguage(), adminAreas); |
+ // Notify EditorView that the fields in the model have changed. EditorView should |
+ // re-read the model and update the UI accordingly. |
+ mHandler.post(mCountryChangeCallback); |
+ } else { |
+ // This should be called when all required fields are put in mAddressField. |
+ setAddressFieldValuesFromCache(); |
+ addAddressFieldsToEditor( |
+ mProfile.getCountryCode(), mProfile.getLanguageCode(), adminAreas); |
+ mEditorView.show(mEditor); |
+ } |
+ } |
+ |
+ /** Requests the list of admin areas. */ |
+ private void loadAdminAreasForCountry(String countryCode) { |
+ // Used to check if the callback is called (for time-out). |
+ mAdminAreasLoaded = false; |
+ |
+ // In each rule, admin area keys are saved under sub-keys of country. |
+ PersonalDataManager.getInstance().loadRulesForSubKeys(countryCode); |
+ PersonalDataManager.getInstance().getRegionSubKeys(countryCode, this); |
+ onAdminAreasLoading(); |
+ } |
+ |
+ /** Cancels the request for the list of admin areas. */ |
+ private void cancelAdminAreasRequest() { |
+ if (mAdminAreasLoaded) return; |
+ onSubKeysReceived(null); |
+ PersonalDataManager.getInstance().cancelPendingGetSubKeys(); |
+ } |
+ |
/** |
- * Adds text fields to the editor model based on the country and language code of the profile |
- * that's being edited. |
+ * In case the the admin areas are not loaded yet, or the time out is set to 0 for testing, |
+ * starts a timer to cancel the request. |
*/ |
- private void addAddressTextFieldsToEditor( |
- EditorModel container, String countryCode, String languageCode) { |
- mAddressUiComponents = mAutofillProfileBridge.getAddressUiComponents(countryCode, |
- languageCode); |
+ public void onAdminAreasLoading() { |
+ if (mAdminAreasLoaded) return; |
+ new Handler().postDelayed(new Runnable() { |
+ @Override |
+ public void run() { |
+ cancelAdminAreasRequest(); |
+ } |
+ }, PersonalDataManager.getInstance().getRequestTimeoutMS()); |
+ } |
+ /** |
+ * Adds fields to the editor model based on the country and language code of |
+ * the profile that's being edited. |
+ */ |
+ private void addAddressFieldsToEditor( |
+ String countryCode, String languageCode, String[] adminAreas) { |
+ mAddressUiComponents = |
+ mAutofillProfileBridge.getAddressUiComponents(countryCode, languageCode); |
+ // In terms of order, country must be the first field. |
+ mEditor.addField(mCountryField); |
for (int i = 0; i < mAddressUiComponents.size(); i++) { |
AddressUiComponent component = mAddressUiComponents.get(i); |
- // The country field is a dropdown, so there's no need to add a text field for it. |
- if (component.id == AddressField.COUNTRY) continue; |
- |
EditorFieldModel field = mAddressFields.get(component.id); |
// Labels depend on country, e.g., state is called province in some countries. These are |
// already localized. |
field.setLabel(component.label); |
field.setIsFullLine(component.isFullLine); |
+ if (component.id == AddressField.ADMIN_AREA && field.isDropdownField()) { |
+ field.setDropdownKeyValues( |
+ mAutofillProfileBridge.getAdminAreaDropdownList(adminAreas)); |
+ } |
+ |
// Libaddressinput formats do not always require the full name (RECIPIENT), but |
// PaymentRequest does. |
if (component.isRequired || component.id == AddressField.RECIPIENT) { |
@@ -295,9 +397,10 @@ public class AddressEditor extends EditorBase<AutofillAddress> { |
} else { |
field.setRequiredErrorMessage(null); |
} |
- |
- container.addField(field); |
+ mEditor.addField(field); |
} |
+ // Phone number must be the last field. |
+ mEditor.addField(mPhoneField); |
} |
private EditorFieldValidator getPhoneValidator() { |