Commit 90bf8242 authored by sebsg's avatar sebsg Committed by Commit bot

[Payments] Dedupe subsets in contact detail suggestions.

Previously suggestions that are a subset of another suggestion are not
deduped.

With this patch, only the most complete version will be suggested to
the user.

A side-effect of this change is that ContactDetails are now sorted by
relevance in regards or the information required from the merchant
instead of completeness.

For example, considering a profile A with only email and a profile B
with email and phone, and a merchant requires email, phone and name.
Previously the order between A and B would have been decided by
frecency since both were incomplete. With this patch, B would be
suggested first since it has more information.

BUG=674647

Review-Url: https://codereview.chromium.org/2583593002
Cr-Commit-Position: refs/heads/master@{#441764}
parent e4fe8017
......@@ -19,6 +19,10 @@ import javax.annotation.Nullable;
public class AutofillContact extends PaymentOption {
private final AutofillProfile mProfile;
private final Context mContext;
private int mCompletionStatus;
private boolean mRequestName;
private boolean mRequestPhone;
private boolean mRequestEmail;
@Nullable private String mPayerName;
@Nullable private String mPayerPhone;
@Nullable private String mPayerEmail;
......@@ -33,13 +37,20 @@ public class AutofillContact extends PaymentOption {
* @param email The email address. If name and phone are empty, this will be the
* primary label.
* @param completionStatus The completion status of this contact.
* @param requestName Whether the merchant requests a payer name.
* @param requestPhone Whether the merchant requests a payer phone number.
* @param requestEmail Whether the merchant requests a payer email address.
*/
public AutofillContact(Context context, AutofillProfile profile, @Nullable String name,
@Nullable String phone, @Nullable String email,
@ContactEditor.CompletionStatus int completionStatus) {
@ContactEditor.CompletionStatus int completionStatus, boolean requestName,
boolean requestPhone, boolean requestEmail) {
super(profile.getGUID(), null, null, null, null);
mContext = context;
mProfile = profile;
mRequestName = requestName;
mRequestPhone = requestPhone;
mRequestEmail = requestEmail;
mIsEditable = true;
setContactInfo(profile.getGUID(), name, phone, email);
updateCompletionStatus(completionStatus);
......@@ -50,16 +61,17 @@ public class AutofillContact extends PaymentOption {
return mPayerName;
}
/** @return Email address. Null if the merchant did not request it or data is incomplete. */
@Nullable public String getPayerEmail() {
return mPayerEmail;
}
/** @return Phone number. Null if the merchant did not request it or data is incomplete. */
@Nullable public String getPayerPhone() {
return mPayerPhone;
}
/** @return Email address. Null if the merchant did not request it or data is incomplete. */
@Nullable
public String getPayerEmail() {
return mPayerEmail;
}
/** @return The autofill profile where this contact data lives. */
public AutofillProfile getProfile() {
return mProfile;
......@@ -87,6 +99,60 @@ public class AutofillContact extends PaymentOption {
updateCompletionStatus(ContactEditor.COMPLETE);
}
/**
* Returns whether this contact is equal or a superset of the specified contact considering the
* information requested by the merchant.
*
* @param contact The contact to compare to.
* @return Whether this contact is equal to or a superset of the other.
*/
public boolean isEqualOrSupersetOf(AutofillContact contact) {
assert contact != null;
// This contact is not equal to or a superset of the other if for a requested field:
// 1- This contact's field is null and the other's is not.
// 2- The field values are not equal.
if (mRequestName) {
if (mPayerName == null && contact.mPayerName != null) return false;
if (mPayerName != null && contact.mPayerName != null
&& !mPayerName.equalsIgnoreCase(contact.mPayerName)) {
return false;
}
}
if (mRequestPhone) {
if (mPayerPhone == null && contact.mPayerPhone != null) return false;
if (mPayerPhone != null && contact.mPayerPhone != null
&& !TextUtils.equals(mPayerPhone, contact.mPayerPhone)) {
return false;
}
}
if (mRequestEmail) {
if (mPayerEmail == null && contact.mPayerEmail != null) return false;
if (mPayerEmail != null && contact.mPayerEmail != null
&& !mPayerEmail.equalsIgnoreCase(contact.mPayerEmail)) {
return false;
}
}
return true;
}
/**
* @return Returns the relevance score of this contact, based on the validity of the information
* requested by the merchant.
*/
public int getRelevanceScore() {
int score = 0;
if (mRequestName && (mCompletionStatus & ContactEditor.INVALID_NAME) == 0) ++score;
if (mRequestPhone && (mCompletionStatus & ContactEditor.INVALID_PHONE_NUMBER) == 0) ++score;
if (mRequestEmail && (mCompletionStatus & ContactEditor.INVALID_EMAIL) == 0) ++score;
return score;
}
private void setContactInfo(String guid, @Nullable String name,
@Nullable String phone, @Nullable String email) {
mPayerName = TextUtils.isEmpty(name) ? null : name;
......@@ -104,6 +170,7 @@ public class AutofillContact extends PaymentOption {
}
private void updateCompletionStatus(int completionStatus) {
mCompletionStatus = completionStatus;
mIsComplete = completionStatus == ContactEditor.COMPLETE;
switch (completionStatus) {
......@@ -123,12 +190,11 @@ public class AutofillContact extends PaymentOption {
mEditMessage = mContext.getString(R.string.payments_phone_number_required);
mEditTitle = mContext.getString(R.string.payments_add_phone_number);
break;
case ContactEditor.INVALID_MULTIPLE_FIELDS:
default:
// Multiple invalid fields.
mEditMessage = mContext.getString(R.string.payments_more_information_required);
mEditTitle = mContext.getString(R.string.payments_add_more_information);
break;
default:
assert false : "Invalid completion status code";
}
}
}
......@@ -4,7 +4,6 @@
package org.chromium.chrome.browser.payments;
import android.support.annotation.IntDef;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Patterns;
......@@ -17,8 +16,6 @@ import org.chromium.chrome.browser.payments.ui.EditorFieldModel;
import org.chromium.chrome.browser.payments.ui.EditorFieldModel.EditorFieldValidator;
import org.chromium.chrome.browser.payments.ui.EditorModel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.HashSet;
import java.util.Set;
......@@ -28,19 +25,15 @@ import javax.annotation.Nullable;
* Contact information editor.
*/
public class ContactEditor extends EditorBase<AutofillContact> {
@IntDef({INVALID_NAME, INVALID_EMAIL, INVALID_PHONE_NUMBER, INVALID_MULTIPLE_FIELDS})
@Retention(RetentionPolicy.SOURCE)
public @interface CompletionStatus {}
/** Can be sent to the merchant as-is without editing first. */
public static final int COMPLETE = 0;
/** The contact name is missing. */
public static final int INVALID_NAME = 1;
public static final int INVALID_NAME = 1 << 0;
/** The contact email is invalid or missing. */
public static final int INVALID_EMAIL = 2;
public static final int INVALID_EMAIL = 1 << 1;
/** The contact phone number is invalid or missing. */
public static final int INVALID_PHONE_NUMBER = 3;
/** Multiple fields are invalid or missing. */
public static final int INVALID_MULTIPLE_FIELDS = 4;
public static final int INVALID_PHONE_NUMBER = 1 << 2;
private final boolean mRequestPayerName;
private final boolean mRequestPayerPhone;
......@@ -80,26 +73,18 @@ public class ContactEditor extends EditorBase<AutofillContact> {
@CompletionStatus
public int checkContactCompletionStatus(
@Nullable String name, @Nullable String phone, @Nullable String email) {
int invalidFieldCount = 0;
int completionStatus = COMPLETE;
if (mRequestPayerName && TextUtils.isEmpty(name)) {
invalidFieldCount++;
completionStatus = INVALID_NAME;
completionStatus |= INVALID_NAME;
}
if (mRequestPayerPhone && !getPhoneValidator().isValid(phone)) {
invalidFieldCount++;
completionStatus = INVALID_PHONE_NUMBER;
completionStatus |= INVALID_PHONE_NUMBER;
}
if (mRequestPayerEmail && !getEmailValidator().isValid(email)) {
invalidFieldCount++;
completionStatus = INVALID_EMAIL;
}
if (invalidFieldCount > 1) {
completionStatus = INVALID_MULTIPLE_FIELDS;
completionStatus |= INVALID_EMAIL;
}
return completionStatus;
......@@ -139,7 +124,8 @@ public class ContactEditor extends EditorBase<AutofillContact> {
final AutofillContact contact = toEdit == null
? new AutofillContact(mContext, new AutofillProfile(), null, null, null,
INVALID_MULTIPLE_FIELDS)
INVALID_NAME | INVALID_PHONE_NUMBER | INVALID_EMAIL, mRequestPayerName,
mRequestPayerPhone, mRequestPayerEmail)
: toEdit;
final EditorFieldModel nameField = mRequestPayerName
......
......@@ -363,11 +363,12 @@ public class PaymentRequestImpl
}
if (requestShipping) {
createShippingSection(profiles);
createShippingSection(Collections.unmodifiableList(profiles));
}
if (requestPayerName || requestPayerPhone || requestPayerEmail) {
createContactSection(profiles, requestPayerName, requestPayerPhone, requestPayerEmail);
createContactSection(Collections.unmodifiableList(profiles), requestPayerName,
requestPayerPhone, requestPayerEmail);
}
mUI = new PaymentRequestUI(mContext, this, requestShipping,
......@@ -391,11 +392,11 @@ public class PaymentRequestImpl
requestPayerPhone, requestShipping, requestPayerName);
}
private void createShippingSection(List<AutofillProfile> profiles) {
private void createShippingSection(List<AutofillProfile> unmodifiableProfiles) {
List<AutofillAddress> addresses = new ArrayList<>();
for (int i = 0; i < profiles.size(); i++) {
AutofillProfile profile = profiles.get(i);
for (int i = 0; i < unmodifiableProfiles.size(); i++) {
AutofillProfile profile = unmodifiableProfiles.get(i);
mAddressEditor.addPhoneNumberIfValid(profile.getPhoneNumber());
// Only suggest addresses that have a street address.
......@@ -440,14 +441,16 @@ public class PaymentRequestImpl
PaymentRequestUI.TYPE_SHIPPING_ADDRESSES, firstCompleteAddressIndex, addresses);
}
private void createContactSection(List<AutofillProfile> profiles, boolean requestName,
boolean requestPhone, boolean requestEmail) {
Set<String> uniqueContactInfos = new HashSet<>();
mContactEditor = new ContactEditor(requestName, requestPhone, requestEmail);
private void createContactSection(List<AutofillProfile> unmodifiableProfiles,
boolean requestName, boolean requestPhone, boolean requestEmail) {
List<AutofillContact> contacts = new ArrayList<>();
List<AutofillContact> uniqueContacts = new ArrayList<>();
mContactEditor = new ContactEditor(requestName, requestPhone, requestEmail);
for (int i = 0; i < profiles.size(); i++) {
AutofillProfile profile = profiles.get(i);
// Add the profile's valid request values to the editor's autocomplete list and convert
// relevant profiles to AutofillContacts.
for (int i = 0; i < unmodifiableProfiles.size(); ++i) {
AutofillProfile profile = unmodifiableProfiles.get(i);
String name = requestName && !TextUtils.isEmpty(profile.getFullName())
? profile.getFullName()
: null;
......@@ -457,44 +460,62 @@ public class PaymentRequestImpl
String email = requestEmail && !TextUtils.isEmpty(profile.getEmailAddress())
? profile.getEmailAddress()
: null;
// Add the values to the editor's autocomplete list.
mContactEditor.addPayerNameIfValid(name);
mContactEditor.addPhoneNumberIfValid(phone);
mContactEditor.addEmailAddressIfValid(email);
// Only create a contact if the profile has relevant information for the merchant.
if (name != null || phone != null || email != null) {
// Different profiles can have identical contact info. Do not add the same
// contact info to the list twice.
String uniqueContactInfo = name + phone + email;
if (!uniqueContactInfos.contains(uniqueContactInfo)) {
uniqueContactInfos.add(uniqueContactInfo);
@ContactEditor.CompletionStatus
int completionStatus =
mContactEditor.checkContactCompletionStatus(name, phone, email);
contacts.add(new AutofillContact(
mContext, profile, name, phone, email, completionStatus));
}
contacts.add(new AutofillContact(mContext, profile, name, phone, email,
mContactEditor.checkContactCompletionStatus(name, phone, email),
requestName, requestPhone, requestEmail));
}
}
// Suggest complete contact infos first.
Collections.sort(contacts, COMPLETENESS_COMPARATOR);
// Order the contacts so the ones that have most of the required information are put first.
// The sort is stable, so contacts with the same relevance score are sorted by frecency.
Collections.sort(contacts, new Comparator<AutofillContact>() {
@Override
public int compare(AutofillContact a, AutofillContact b) {
return b.getRelevanceScore() - a.getRelevanceScore();
}
});
// This algorithm is quadratic, but since the number of contacts is generally very small
// ( < 10) a faster but more complicated algorithm would be overkill.
for (int i = 0; i < contacts.size(); i++) {
AutofillContact contact = contacts.get(i);
// Different contacts can have identical info. Do not add the same contact info or a
// subset of it twice. It's important that the profiles be sorted by the quantity of
// required info they have.
boolean isNewSuggestion = true;
for (int j = 0; j < uniqueContacts.size(); ++j) {
if (uniqueContacts.get(j).isEqualOrSupersetOf(contact)) {
isNewSuggestion = false;
break;
}
}
if (isNewSuggestion) uniqueContacts.add(contact);
// Limit the number of suggestions.
contacts = contacts.subList(0, Math.min(contacts.size(), SUGGESTIONS_LIMIT));
// Limit the number of suggestions.
if (uniqueContacts.size() == SUGGESTIONS_LIMIT) break;
}
// Log the number of suggested contact infos.
mJourneyLogger.setNumberOfSuggestionsShown(
PaymentRequestJourneyLogger.SECTION_CONTACT_INFO, contacts.size());
PaymentRequestJourneyLogger.SECTION_CONTACT_INFO, uniqueContacts.size());
// Automatically select the first address if it is complete.
int firstCompleteContactIndex = SectionInformation.NO_SELECTION;
if (!contacts.isEmpty() && contacts.get(0).isComplete()) {
if (!uniqueContacts.isEmpty() && uniqueContacts.get(0).isComplete()) {
firstCompleteContactIndex = 0;
}
mContactSection = new SectionInformation(
PaymentRequestUI.TYPE_CONTACT_DETAILS, firstCompleteContactIndex, contacts);
PaymentRequestUI.TYPE_CONTACT_DETAILS, firstCompleteContactIndex, uniqueContacts);
}
/**
......
......@@ -1353,6 +1353,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestDynamicShippingSingleAddressTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndFreeShippingTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestEmailAndPhoneTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestExpiredLocalCardTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestExtraShippingOptionsTest.java",
"javatests/src/org/chromium/chrome/browser/payments/PaymentRequestFailCompleteTest.java",
......@@ -1531,6 +1532,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/omaha/ResponseParserTest.java",
"junit/src/org/chromium/chrome/browser/omaha/VersionNumberTest.java",
"junit/src/org/chromium/chrome/browser/payments/AutofillContactTest.java",
"junit/src/org/chromium/chrome/browser/payments/AutofillContactUnitTest.java",
"junit/src/org/chromium/chrome/browser/payments/CurrencyStringFormatterUnitTest.java",
"junit/src/org/chromium/chrome/browser/snackbar/SnackbarCollectionUnitTest.java",
"junit/src/org/chromium/chrome/browser/superviseduser/SupervisedUserContentProviderUnitTest.java",
......
......@@ -34,6 +34,26 @@ public class PaymentRequestContactDetailsTest extends PaymentRequestTestBase {
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
// Add the same profile but with a different address.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "", "Google",
"999 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
// Add the same profile but without a phone number.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "" /* phone_number */,
"jon.doe@google.com", "en-US"));
// Add the same profile but without an email.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"" /* emailAddress */, "en-US"));
// Add the same profile but without a name.
helper.setProfile(new AutofillProfile("" /* name */, "https://example.com", true, "",
"Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
}
......@@ -207,6 +227,19 @@ public class PaymentRequestContactDetailsTest extends PaymentRequestTestBase {
expectResultContains(new String[] {"Request cancelled"});
}
/**
* Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
* to the user.
*/
@MediumTest
@Feature({"Payments"})
public void testSuggestionsDeduped()
throws InterruptedException, ExecutionException, TimeoutException {
triggerUIAndWait(mReadyToPay);
clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
assertEquals(1, getNumberOfContactDetailSuggestions());
}
/**
* Test that starting a payment request that requires the user's email address, phone number and
* name results in the appropriate metric being logged in the
......
// 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.support.test.filters.MediumTest;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.autofill.AutofillTestHelper;
import org.chromium.chrome.browser.autofill.PersonalDataManager.AutofillProfile;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
/**
* A payment integration test for a merchant that requests email address and a phone number.
*/
public class PaymentRequestEmailAndPhoneTest extends PaymentRequestTestBase {
public PaymentRequestEmailAndPhoneTest() {
// This merchant request an email address and a phone number.
super("payment_request_email_and_phone_test.html");
}
@Override
public void onMainActivityStarted()
throws InterruptedException, ExecutionException, TimeoutException {
AutofillTestHelper helper = new AutofillTestHelper();
// The user has a valid email address and phone number on disk.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
// Add the same profile but with a different address.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "", "Google",
"999 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
// Add the same profile but without a phone number.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "" /* phoneNumber */,
"jon.doe@google.com", "en-US"));
// Add the same profile but without an email.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"" /* emailAddress */, "en-US"));
// Add the same profile but without a name.
helper.setProfile(new AutofillProfile("" /* name */, "https://example.com", true, "",
"Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
}
/** Provide the existing valid email address and phone number to the merchant. */
@MediumTest
@Feature({"Payments"})
public void testPay() throws InterruptedException, ExecutionException, TimeoutException {
triggerUIAndWait(mReadyToPay);
clickAndWait(R.id.button_primary, mDismissed);
expectResultContains(new String[] {"555-555-5555", "jon.doe@google.com"});
}
/** Attempt to add an invalid email address and phone number and cancel the transaction. */
@MediumTest
@Feature({"Payments"})
public void testAddInvalidEmailAndCancel()
throws InterruptedException, ExecutionException, TimeoutException {
triggerUIAndWait(mReadyToPay);
clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
clickInContactInfoAndWait(R.id.payments_add_option_button, mReadyToEdit);
setTextInEditorAndWait(new String[] {"-1-", "jane.jones"}, mEditorTextUpdate);
clickInEditorAndWait(R.id.payments_edit_done_button, mEditorValidationError);
clickInEditorAndWait(R.id.payments_edit_cancel_button, mReadyToPay);
clickAndWait(R.id.close_button, mDismissed);
expectResultContains(new String[] {"Request cancelled"});
}
/** Add a new email address and phone number and provide that to the merchant. */
@MediumTest
@Feature({"Payments"})
public void testAddEmailAndPhoneAndPay()
throws InterruptedException, ExecutionException, TimeoutException {
triggerUIAndWait(mReadyToPay);
clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
clickInContactInfoAndWait(R.id.payments_add_option_button, mReadyToEdit);
setTextInEditorAndWait(
new String[] {"555-555-5555", "jane.jones@google.com"}, mEditorTextUpdate);
clickInEditorAndWait(R.id.payments_edit_done_button, mReadyToPay);
clickAndWait(R.id.button_primary, mDismissed);
expectResultContains(new String[] {"555-555-5555", "jane.jones@google.com"});
}
/**
* Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
* to the user.
*/
@MediumTest
@Feature({"Payments"})
public void testSuggestionsDeduped()
throws InterruptedException, ExecutionException, TimeoutException {
triggerUIAndWait(mReadyToPay);
clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
assertEquals(1, getNumberOfContactDetailSuggestions());
}
/**
* Test that starting a payment request that requires only the user's email address results in
* the appropriate metric being logged in the PaymentRequest.RequestedInformation histogram.
*/
@MediumTest
@Feature({"Payments"})
public void testRequestedInformationMetric()
throws InterruptedException, ExecutionException, TimeoutException {
// Start the Payment Request.
triggerUIAndWait(mReadyToPay);
int appropriateEnumValue = PaymentRequestMetrics.REQUESTED_INFORMATION_EMAIL
| PaymentRequestMetrics.REQUESTED_INFORMATION_PHONE;
// Make sure that only the appropriate enum value was logged.
for (int i = 0; i < PaymentRequestMetrics.REQUESTED_INFORMATION_MAX; ++i) {
assertEquals((i == (appropriateEnumValue) ? 1 : 0),
RecordHistogram.getHistogramValueCountForTesting(
"PaymentRequest.RequestedInformation", i));
}
}
}
......@@ -33,6 +33,26 @@ public class PaymentRequestEmailTest extends PaymentRequestTestBase {
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
// Add the same profile but with a different address.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "", "Google",
"999 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
// Add the same profile but without a phone number.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "" /* phone_number */,
"jon.doe@google.com", "en-US"));
// Add the same profile but without an email.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"" /* emailAddress */, "en-US"));
// Add the same profile but without a name.
helper.setProfile(new AutofillProfile("" /* name */, "https://example.com", true, "",
"Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
}
......@@ -75,6 +95,19 @@ public class PaymentRequestEmailTest extends PaymentRequestTestBase {
expectResultContains(new String[] {"jane.jones@google.com"});
}
/**
* Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
* to the user.
*/
@MediumTest
@Feature({"Payments"})
public void testSuggestionsDeduped()
throws InterruptedException, ExecutionException, TimeoutException {
triggerUIAndWait(mReadyToPay);
clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
assertEquals(1, getNumberOfContactDetailSuggestions());
}
/**
* Test that starting a payment request that requires only the user's email address results in
* the appropriate metric being logged in the PaymentRequest.RequestedInformation histogram.
......
......@@ -21,36 +21,69 @@ import java.util.concurrent.TimeoutException;
*/
public class PaymentRequestMultipleContactDetailsTest extends PaymentRequestTestBase {
private static final AutofillProfile[] AUTOFILL_PROFILES = {
// Incomplete (no phone) profile.
// 0 - Incomplete (no phone) profile.
new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
"Bart Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
"90210", "", "US", "", "bart@simpson.com", ""),
// Incomplete (no email) profile.
// 1 - Incomplete (no email) profile.
new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
"Homer Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
"90210", "", "US", "555 123-4567", "", ""),
// Complete profile.
// 2 - Complete profile.
new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
"Lisa Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
"90210", "", "US", "555 123-4567", "lisa@simpson.com", ""),
// Complete profile.
// 3 - Complete profile.
new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
"Maggie Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
"90210", "", "US", "555 123-4567", "maggie@simpson.com", ""),
// Incomplete (no phone and email) profile.
// 4 - Incomplete (no phone and email) profile.
new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
"Marge Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
"90210", "", "US", "", "", ""),
// Incomplete (no name) profile.
// 5 - Incomplete (no name) profile.
new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */, "",
"Acme Inc.", "123 Main", "California", "Los Angeles", "", "90210", "", "US",
"555 123-4567", "marge@simpson.com", ""),
// These profiles are used to test the dedupe of subset suggestions. They are based on
// The Lisa Simpson profile.
// 6 - Same as original, but with no name.
new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
"" /* name */, "Acme Inc.", "123 Main", "California", "Los Angeles", "",
"90210", "", "US", "555 123-4567", "lisa@simpson.com", ""),
// 7 - Same as original, but with no phone.
new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
"Lisa Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
"90210", "", "US", "" /* phoneNumber */, "lisa@simpson.com", ""),
// 8 - Same as original, but with no email.
new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
"Lisa Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
"90210", "", "US", "555 123-4567", "" /* emailAddress */, ""),
// 9 - Same as original, but with no phone and no email.
new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
"Lisa Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
"90210", "", "US", "" /* phoneNumber */, "" /* emailAddress */, ""),
// 10 - Has an email address that is a superset of the original profile's email.
new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
"Lisa Simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
"90210", "", "US", "555 123-4567", "fakelisa@simpson.com", ""),
// 11 - Has the same name as the original but with no capitalization in the name.
new AutofillProfile("" /* guid */, "https://www.example.com" /* origin */,
"lisa simpson", "Acme Inc.", "123 Main", "California", "Los Angeles", "",
"90210", "", "US", "555 123-4567", "lisa@simpson.com", ""),
};
private AutofillProfile[] mProfilesToAdd;
......@@ -131,9 +164,80 @@ public class PaymentRequestMultipleContactDetailsTest extends PaymentRequestTest
getContactDetailsSuggestionLabel(0));
assertEquals(
"Homer Simpson\n555 123-4567\nEmail required", getContactDetailsSuggestionLabel(1));
assertEquals(
"Marge Simpson\nMore information required", getContactDetailsSuggestionLabel(2));
assertEquals("555 123-4567\nmarge@simpson.com\nName required",
getContactDetailsSuggestionLabel(3));
getContactDetailsSuggestionLabel(2));
assertEquals(
"Marge Simpson\nMore information required", getContactDetailsSuggestionLabel(3));
}
/**
* Makes sure that suggestions that are subsets of other fields (empty values) are deduped.
*/
@MediumTest
@Feature({"Payments"})
public void testContactDetailsDedupe_EmptyFields()
throws InterruptedException, ExecutionException, TimeoutException {
// Add the original profile and a bunch of similar profiles with missing fields.
// Make sure the original profile is suggested last, to test that the suggestions are
// sorted by completeness.
mProfilesToAdd = new AutofillProfile[] {
AUTOFILL_PROFILES[2], AUTOFILL_PROFILES[6], AUTOFILL_PROFILES[7],
AUTOFILL_PROFILES[8], AUTOFILL_PROFILES[9],
};
mCountsToSet = new int[] {1, 20, 15, 10, 5};
mDatesToSet = new int[] {1000, 4000, 3000, 2000, 1000};
triggerUIAndWait(mReadyForInput);
clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
// Only the original profile with all the fields should be suggested.
assertEquals(1, getNumberOfContactDetailSuggestions());
assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
getContactDetailsSuggestionLabel(0));
}
/**
* Makes sure that suggestions where some fields values are equal but with different case are
* deduped.
*/
@MediumTest
@Feature({"Payments"})
public void testContactDetailsDedupe_Capitalization()
throws InterruptedException, ExecutionException, TimeoutException {
// Add the original profile and the one where the the name is not capitalized.
// Make sure the original profile is suggested first (no particular reason).
mProfilesToAdd = new AutofillProfile[] {AUTOFILL_PROFILES[2], AUTOFILL_PROFILES[11]};
mCountsToSet = new int[] {15, 5};
mDatesToSet = new int[] {5000, 2000};
triggerUIAndWait(mReadyForInput);
clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
assertEquals(1, getNumberOfContactDetailSuggestions());
assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
getContactDetailsSuggestionLabel(0));
}
/**
* Makes sure that suggestions where some fields values are subsets of the other are not
* deduped.
*/
@MediumTest
@Feature({"Payments"})
public void testContactDetailsDontDedupe_FieldSubset()
throws InterruptedException, ExecutionException, TimeoutException {
// Add the original profile and the one where the email is a superset of the original.
// Make sure the one with the superset is suggested first, because to test the subset one
// needs to be added after.
mProfilesToAdd = new AutofillProfile[] {AUTOFILL_PROFILES[2], AUTOFILL_PROFILES[10]};
mCountsToSet = new int[] {15, 25};
mDatesToSet = new int[] {5000, 7000};
triggerUIAndWait(mReadyForInput);
clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
assertEquals(2, getNumberOfContactDetailSuggestions());
assertEquals("Lisa Simpson\n555 123-4567\nfakelisa@simpson.com",
getContactDetailsSuggestionLabel(0));
assertEquals("Lisa Simpson\n555 123-4567\nlisa@simpson.com",
getContactDetailsSuggestionLabel(1));
}
}
......@@ -37,6 +37,26 @@ public class PaymentRequestNameTest extends PaymentRequestTestBase {
helper.setCreditCard(new CreditCard("", "https://example.com", true, true, "Jon Doe",
"4111111111111111", "1111", "12", "2050", "visa", R.drawable.pr_visa,
billingAddressId, "" /* serverId */));
// Add the same profile but with a different address.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "", "Google",
"999 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
// Add the same profile but without a phone number.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "" /* phone_number */,
"jon.doe@google.com", "en-US"));
// Add the same profile but without an email.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"" /* emailAddress */, "en-US"));
// Add the same profile but without a name.
helper.setProfile(new AutofillProfile("" /* name */, "https://example.com", true, "",
"Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
}
/** Provide the existing valid payer name to the merchant. */
......@@ -81,6 +101,19 @@ public class PaymentRequestNameTest extends PaymentRequestTestBase {
expectResultContains(new String[] {"Jane Jones"});
}
/**
* Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
* to the user.
*/
@MediumTest
@Feature({"Payments"})
public void testSuggestionsDeduped()
throws InterruptedException, ExecutionException, TimeoutException {
triggerUIAndWait(mReadyToPay);
clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
assertEquals(1, getNumberOfContactDetailSuggestions());
}
/**
* Test that starting a payment request that requires only the user's payer name results in
* the appropriate metric being logged in the PaymentRequest.RequestedInformation histogram.
......
......@@ -33,6 +33,26 @@ public class PaymentRequestPhoneTest extends PaymentRequestTestBase {
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
// Add the same profile but with a different address.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "", "Google",
"999 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
// Add the same profile but without a phone number.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "" /* phone_number */,
"jon.doe@google.com", "en-US"));
// Add the same profile but without an email.
helper.setProfile(new AutofillProfile("", "https://example.com", true, "Jon Doe", "Google",
"340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"" /* emailAddress */, "en-US"));
// Add the same profile but without a name.
helper.setProfile(new AutofillProfile("" /* name */, "https://example.com", true, "",
"Google", "340 Main St", "CA", "Los Angeles", "", "90291", "", "US", "555-555-5555",
"jon.doe@google.com", "en-US"));
installPaymentApp(HAVE_INSTRUMENTS, IMMEDIATE_RESPONSE);
}
......@@ -75,6 +95,19 @@ public class PaymentRequestPhoneTest extends PaymentRequestTestBase {
expectResultContains(new String[] {"999-999-9999"});
}
/**
* Makes sure that suggestions that are equal to or subsets of other suggestions are not shown
* to the user.
*/
@MediumTest
@Feature({"Payments"})
public void testSuggestionsDeduped()
throws InterruptedException, ExecutionException, TimeoutException {
triggerUIAndWait(mReadyToPay);
clickInContactInfoAndWait(R.id.payments_section, mReadyForInput);
assertEquals(1, getNumberOfContactDetailSuggestions());
}
/**
* Test that starting a payment request that requires only the user's phone number results in
* the appropriate metric being logged in the PaymentRequest.RequestedInformation histogram.
......
......@@ -24,7 +24,7 @@ import java.util.Arrays;
import java.util.Collection;
/**
* Unit tests for the AutofillContact class.
* Parametrized unit tests for the AutofillContact class.
*/
@RunWith(ParameterizedRobolectricTestRunner.class)
@Config(sdk = 21, manifest = Config.NONE)
......@@ -107,8 +107,9 @@ public class AutofillContactTest {
public void test() {
AutofillProfile profile = new AutofillProfile();
AutofillContact contact = new AutofillContact(mContext, profile, mPayerName, mPayerPhone,
mPayerEmail,
mIsComplete ? ContactEditor.COMPLETE : ContactEditor.INVALID_MULTIPLE_FIELDS);
mPayerEmail, mIsComplete ? ContactEditor.COMPLETE
: ContactEditor.INVALID_NAME | ContactEditor.INVALID_EMAIL,
true, true, true);
Assert.assertEquals(
mIsComplete ? "Contact should be complete" : "Contact should be incomplete",
......
/*
* 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.
*/
/* global PaymentRequest:false */
/*
* Launches the PaymentRequest UI that requests email address and phone number.
*/
function buy() { // eslint-disable-line no-unused-vars
try {
new PaymentRequest(
[{supportedMethods: ['https://bobpay.com']}],
{total: {label: 'Total', amount: {currency: 'USD', value: '5.00'}}},
{requestPayerEmail: true, requestPayerPhone: true})
.show()
.then(function(resp) {
resp.complete('success')
.then(function() {
print(JSON.stringify(resp, undefined, 2));
})
.catch(function(error) {
print(error);
});
})
.catch(function(error) {
print(error);
});
} catch (error) {
print(error.message);
}
}
<!DOCTYPE html>
<!--
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.
-->
<html>
<head>
<title>Email and Phone Test</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<button onclick="buy()" id="buy">Email and Phone Test</button>
<pre id="result"></pre>
<script src="util.js"></script>
<script src="email_and_phone.js"></script>
</body>
</html>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment