blob: 3e505048e631c7a80dea4354773982275d576957 [file] [log] [blame]
// Copyright 2016 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.
package org.chromium.chrome.browser.payments;
import android.content.Context;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Pair;
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.preferences.autofill.AutofillProfileBridge;
import org.chromium.chrome.browser.preferences.autofill.AutofillProfileBridge.AddressField;
import org.chromium.chrome.browser.widget.prefeditor.EditableOption;
import org.chromium.payments.mojom.PaymentAddress;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The locally stored autofill address.
*/
public class AutofillAddress extends EditableOption {
/** The pattern for a valid region code. */
private static final String REGION_CODE_PATTERN = "^[A-Z]{2}$";
// Language/script code pattern and capture group numbers.
private static final String LANGUAGE_SCRIPT_CODE_PATTERN =
"^([a-z]{2})(-([A-Z][a-z]{3}))?(-[A-Za-z]+)*$";
private static final int LANGUAGE_CODE_GROUP = 1;
private static final int SCRIPT_CODE_GROUP = 3;
@IntDef({CompletionStatus.COMPLETE, CompletionStatus.INVALID_ADDRESS,
CompletionStatus.INVALID_PHONE_NUMBER, CompletionStatus.INVALID_RECIPIENT,
CompletionStatus.INVALID_MULTIPLE_FIELDS})
@Retention(RetentionPolicy.SOURCE)
public @interface CompletionStatus {
/** Can be sent to the merchant as-is without editing first. */
int COMPLETE = 0;
/** The address is invalid. For example, missing state or city name. */
int INVALID_ADDRESS = 1;
/** The phone number is invalid or missing. */
int INVALID_PHONE_NUMBER = 2;
/** The recipient is missing. */
int INVALID_RECIPIENT = 3;
/** Multiple fields are invalid or missing. */
int INVALID_MULTIPLE_FIELDS = 4;
}
@IntDef({CompletenessCheckType.NORMAL, CompletenessCheckType.IGNORE_PHONE})
@Retention(RetentionPolicy.SOURCE)
public @interface CompletenessCheckType {
/** A normal completeness check. */
int NORMAL = 0;
/** A completeness check that ignores phone numbers. */
int IGNORE_PHONE = 1;
}
@Nullable private static Pattern sRegionCodePattern;
private Context mContext;
private AutofillProfile mProfile;
@Nullable private Pattern mLanguageScriptCodePattern;
@Nullable private String mShippingLabelWithCountry;
@Nullable private String mShippingLabelWithoutCountry;
@Nullable private String mBillingLabel;
/**
* Builds the autofill address.
*
* @param profile The autofill profile containing the address information.
*/
public AutofillAddress(Context context, AutofillProfile profile) {
super(profile.getGUID(), profile.getFullName(), profile.getLabel(),
profile.getPhoneNumber(), null);
mContext = context;
mProfile = profile;
mIsEditable = true;
checkAndUpdateAddressCompleteness();
}
/** @return The autofill profile where this address data lives. */
public AutofillProfile getProfile() {
return mProfile;
}
/**
* Updates the address and marks it "complete." Called after the user has edited this address.
* Updates the identifier and labels.
*
* @param profile The new profile to use.
*/
public void completeAddress(AutofillProfile profile) {
// Since the profile changed, our cached labels are now out of date. Set them to null so the
// labels are recomputed next time they are needed.
mShippingLabelWithCountry = null;
mShippingLabelWithoutCountry = null;
mBillingLabel = null;
mProfile = profile;
updateIdentifierAndLabels(mProfile.getGUID(), mProfile.getFullName(), mProfile.getLabel(),
mProfile.getPhoneNumber());
checkAndUpdateAddressCompleteness();
assert mIsComplete;
}
/**
* Gets the shipping address label which includes the country for the profile associated with
* this address and sets it as sublabel for this EditableOption.
*/
public void setShippingAddressLabelWithCountry() {
assert mProfile != null;
if (mShippingLabelWithCountry == null) {
mShippingLabelWithCountry =
PersonalDataManager.getInstance()
.getShippingAddressLabelWithCountryForPaymentRequest(mProfile);
}
mProfile.setLabel(mShippingLabelWithCountry);
updateSublabel(mProfile.getLabel());
}
/**
* Gets the shipping address label which does not include the country for the profile associated
* with this address and sets it as sublabel for this EditableOption.
*/
public void setShippingAddressLabelWithoutCountry() {
assert mProfile != null;
if (mShippingLabelWithoutCountry == null) {
mShippingLabelWithoutCountry =
PersonalDataManager.getInstance()
.getShippingAddressLabelWithoutCountryForPaymentRequest(mProfile);
}
mProfile.setLabel(mShippingLabelWithoutCountry);
updateSublabel(mProfile.getLabel());
}
/*
* Gets the billing address label for the profile associated with this address and sets it as
* sublabel for this EditableOption.
*/
public void setBillingAddressLabel() {
assert mProfile != null;
if (mBillingLabel == null) {
mBillingLabel =
PersonalDataManager.getInstance().getBillingAddressLabelForPaymentRequest(
mProfile);
}
mProfile.setLabel(mBillingLabel);
updateSublabel(mProfile.getLabel());
}
/**
* Checks whether this address is complete and updates edit message, edit title and complete
* status.
*/
private void checkAndUpdateAddressCompleteness() {
Pair<Integer, Integer> messageResIds = getEditMessageAndTitleResIds(
checkAddressCompletionStatus(mProfile, CompletenessCheckType.NORMAL));
mEditMessage = messageResIds.first.intValue() == 0
? null
: mContext.getString(messageResIds.first);
mEditTitle = messageResIds.second.intValue() == 0
? null
: mContext.getString(messageResIds.second);
mIsComplete = mEditMessage == null;
}
/**
* Gets the edit message and title resource Ids for the completion status.
*
* @param completionStatus The completion status.
* @return The resource Ids. The first is the edit message resource Id. The second is the
* correspond editor title resource Id.
*/
public static Pair<Integer, Integer> getEditMessageAndTitleResIds(
@CompletionStatus int completionStatus) {
int editMessageResId = 0;
int editTitleResId = 0;
switch (completionStatus) {
case CompletionStatus.COMPLETE:
editTitleResId = R.string.payments_edit_address;
break;
case CompletionStatus.INVALID_ADDRESS:
editMessageResId = R.string.payments_invalid_address;
editTitleResId = R.string.payments_add_valid_address;
break;
case CompletionStatus.INVALID_PHONE_NUMBER:
editMessageResId = R.string.payments_phone_number_required;
editTitleResId = R.string.payments_add_phone_number;
break;
case CompletionStatus.INVALID_RECIPIENT:
editMessageResId = R.string.payments_recipient_required;
editTitleResId = R.string.payments_add_recipient;
break;
case CompletionStatus.INVALID_MULTIPLE_FIELDS:
editMessageResId = R.string.payments_more_information_required;
editTitleResId = R.string.payments_add_more_information;
break;
default:
assert false : "Invalid completion status";
}
return new Pair<Integer, Integer>(editMessageResId, editTitleResId);
}
/**
* Checks address completion status in the given profile.
*
* If the country code is not set or invalid, but all fields for the default locale's country
* code are present, then the profile is deemed "complete." AutoflllAddress.toPaymentAddress()
* will use the default locale to fill in a blank country code before sending the address to the
* renderer.
*
* @param profile The autofill profile containing the address information.
* @return int The completion status.
*/
@CompletionStatus
public static int checkAddressCompletionStatus(
AutofillProfile profile, @CompletenessCheckType int checkType) {
int invalidFieldsCount = 0;
@CompletionStatus
int completionStatus = CompletionStatus.COMPLETE;
if (checkType != CompletenessCheckType.IGNORE_PHONE
&& !PhoneNumberUtils.isGlobalPhoneNumber(
PhoneNumberUtils.stripSeparators(profile.getPhoneNumber().toString()))) {
completionStatus = CompletionStatus.INVALID_PHONE_NUMBER;
invalidFieldsCount++;
}
List<Integer> requiredFields = AutofillProfileBridge.getRequiredAddressFields(
AutofillAddress.getCountryCode(profile));
for (int fieldId : requiredFields) {
if (fieldId == AddressField.RECIPIENT || fieldId == AddressField.COUNTRY) continue;
if (!TextUtils.isEmpty(getProfileField(profile, fieldId))) continue;
completionStatus = CompletionStatus.INVALID_ADDRESS;
invalidFieldsCount++;
break;
}
if (TextUtils.isEmpty(profile.getFullName())) {
completionStatus = CompletionStatus.INVALID_RECIPIENT;
invalidFieldsCount++;
}
if (invalidFieldsCount > 1) {
completionStatus = CompletionStatus.INVALID_MULTIPLE_FIELDS;
}
return completionStatus;
}
/** @return The given autofill profile field. */
public static String getProfileField(AutofillProfile profile, int field) {
assert profile != null;
switch (field) {
case AddressField.COUNTRY:
return profile.getCountryCode();
case AddressField.ADMIN_AREA:
return profile.getRegion();
case AddressField.LOCALITY:
return profile.getLocality();
case AddressField.DEPENDENT_LOCALITY:
return profile.getDependentLocality();
case AddressField.SORTING_CODE:
return profile.getSortingCode();
case AddressField.POSTAL_CODE:
return profile.getPostalCode();
case AddressField.STREET_ADDRESS:
return profile.getStreetAddress();
case AddressField.ORGANIZATION:
return profile.getCompanyName();
case AddressField.RECIPIENT:
return profile.getFullName();
}
assert false;
return null;
}
/** @return The country code to use, e.g., when constructing an editor for this address. */
public static String getCountryCode(@Nullable AutofillProfile profile) {
if (sRegionCodePattern == null) sRegionCodePattern = Pattern.compile(REGION_CODE_PATTERN);
return profile == null || TextUtils.isEmpty(profile.getCountryCode())
|| !sRegionCodePattern.matcher(profile.getCountryCode()).matches()
? Locale.getDefault().getCountry() : profile.getCountryCode();
}
/** @return The address for the merchant. */
public PaymentAddress toPaymentAddress() {
assert mIsComplete;
PaymentAddress result = new PaymentAddress();
result.country = getCountryCode(mProfile);
result.addressLine = mProfile.getStreetAddress().split("\n");
result.region = mProfile.getRegion();
result.city = mProfile.getLocality();
result.dependentLocality = mProfile.getDependentLocality();
result.postalCode = mProfile.getPostalCode();
result.sortingCode = mProfile.getSortingCode();
result.organization = mProfile.getCompanyName();
result.recipient = mProfile.getFullName();
result.languageCode = "";
result.scriptCode = "";
result.phone = mProfile.getPhoneNumber();
if (mProfile.getLanguageCode() == null) return result;
if (mLanguageScriptCodePattern == null) {
mLanguageScriptCodePattern = Pattern.compile(LANGUAGE_SCRIPT_CODE_PATTERN);
}
Matcher matcher = mLanguageScriptCodePattern.matcher(mProfile.getLanguageCode());
if (matcher.matches()) {
result.languageCode = ensureNotNull(matcher.group(LANGUAGE_CODE_GROUP));
result.scriptCode = ensureNotNull(matcher.group(SCRIPT_CODE_GROUP));
}
return result;
}
private static String ensureNotNull(@Nullable String value) {
return value == null ? "" : value;
}
}