Refactor contact editor controller into its own class.

BUG=603635

Review-Url: https://codereview.chromium.org/2092083003
Cr-Commit-Position: refs/heads/master@{#402352}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ContactEditor.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ContactEditor.java
new file mode 100644
index 0000000..ddc48d6
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ContactEditor.java
@@ -0,0 +1,191 @@
+// 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.telephony.PhoneNumberUtils;
+import android.util.Patterns;
+
+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.payments.ui.EditorFieldModel;
+import org.chromium.chrome.browser.payments.ui.EditorFieldModel.EditorFieldValidator;
+import org.chromium.chrome.browser.payments.ui.EditorModel;
+import org.chromium.chrome.browser.payments.ui.EditorView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+/**
+ * Contact information editor.
+ */
+public class ContactEditor {
+    private final boolean mRequestPayerPhone;
+    private final boolean mRequestPayerEmail;
+    private final List<CharSequence> mPhoneNumbers;
+    private final List<CharSequence> mEmailAddresses;
+    @Nullable private EditorView mEditorView;
+    @Nullable private Context mContext;
+    @Nullable private EditorFieldValidator mPhoneValidator;
+    @Nullable private EditorFieldValidator mEmailValidator;
+
+    /**
+     * Builds a contact information editor.
+     *
+     * @param requestPayerPhone Whether to request the user's phone number.
+     * @param requestPayerEmail Whether to request the user's email address.
+     */
+    public ContactEditor(boolean requestPayerPhone, boolean requestPayerEmail) {
+        assert requestPayerPhone || requestPayerEmail;
+        mRequestPayerPhone = requestPayerPhone;
+        mRequestPayerEmail = requestPayerEmail;
+        mPhoneNumbers = new ArrayList<>();
+        mEmailAddresses = new ArrayList<>();
+    }
+
+    /**
+     * Returns whether the following contact information can be sent to the merchant as-is without
+     * editing first.
+     *
+     * @param phone The phone number to check.
+     * @param email The email address to check.
+     * @return Whether the contact information is complete.
+     */
+    public boolean isContactInformationComplete(@Nullable String phone, @Nullable String email) {
+        return (!mRequestPayerPhone || getPhoneValidator().isValid(phone))
+                && (!mRequestPayerEmail || getEmailValidator().isValid(email));
+    }
+
+    /**
+     * Sets the user interface to be used for editing contact information.
+     *
+     * @param editorView The user interface to be used.
+     */
+    public void setEditorView(EditorView editorView) {
+        assert editorView != null;
+        mEditorView = editorView;
+        mContext = mEditorView.getContext();
+    }
+
+    /**
+     * Adds the given phone number to the autocomplete list, if it's valid.
+     *
+     * @param phoneNumber The phone number to possibly add.
+     */
+    public void addPhoneNumberIfValid(@Nullable CharSequence phoneNumber) {
+        if (getPhoneValidator().isValid(phoneNumber)) mPhoneNumbers.add(phoneNumber);
+    }
+
+    /**
+     * Adds the given email address to the autocomplete list, if it's valid.
+     *
+     * @param emailAddress The email address to possibly add.
+     */
+    public void addEmailAddressIfValid(@Nullable CharSequence emailAddress) {
+        if (getEmailValidator().isValid(emailAddress)) mEmailAddresses.add(emailAddress);
+    }
+
+    /**
+     * Shows the user interface for editing the given contact. The contact is also updated on disk,
+     * so there's no need to do that in the calling code.
+     *
+     * @param toEdit   The contact to edit. Can be null if the user is adding a new contact instead
+     *                 of editing an existing one.
+     * @param callback The callback to invoke with the complete and valid contact information. Can
+     *                 be invoked with null if the user clicked Cancel.
+     */
+    public void editContact(
+            @Nullable AutofillContact toEdit, final Callback<AutofillContact> callback) {
+        assert mEditorView != null;
+        assert mContext != null;
+
+        final AutofillContact contact = toEdit == null
+                ? new AutofillContact(new AutofillProfile(), null, null, false) : toEdit;
+
+        final EditorFieldModel phoneField = mRequestPayerPhone
+                ? new EditorFieldModel(EditorFieldModel.INPUT_TYPE_HINT_PHONE,
+                          mContext.getString(R.string.autofill_profile_editor_phone_number),
+                          mPhoneNumbers, getPhoneValidator(),
+                          mContext.getString(R.string.payments_phone_required_validation_message),
+                          mContext.getString(R.string.payments_phone_invalid_validation_message),
+                          contact.getPayerPhone())
+                : null;
+
+        final EditorFieldModel emailField = mRequestPayerEmail
+                ? new EditorFieldModel(EditorFieldModel.INPUT_TYPE_HINT_EMAIL,
+                          mContext.getString(R.string.autofill_profile_editor_email_address),
+                          mEmailAddresses, getEmailValidator(),
+                          mContext.getString(R.string.payments_email_required_validation_message),
+                          mContext.getString(R.string.payments_email_invalid_validation_message),
+                          contact.getPayerEmail())
+                : null;
+
+        EditorModel editor =
+                new EditorModel(mContext.getString(R.string.payments_add_contact_details_label));
+        if (phoneField != null) editor.addField(phoneField);
+        if (emailField != null) editor.addField(emailField);
+
+        editor.setCancelCallback(new Runnable() {
+            @Override
+            public void run() {
+                callback.onResult(null);
+            }
+        });
+
+        editor.setDoneCallback(new Runnable() {
+            @Override
+            public void run() {
+                String phone = null;
+                String email = null;
+
+                if (phoneField != null) {
+                    phone = phoneField.getValue().toString();
+                    contact.getProfile().setPhoneNumber(phone);
+                }
+
+                if (emailField != null) {
+                    email = emailField.getValue().toString();
+                    contact.getProfile().setEmailAddress(email);
+                }
+
+                PersonalDataManager.getInstance().setProfile(contact.getProfile());
+                contact.completeContact(phone, email);
+                callback.onResult(contact);
+            }
+        });
+
+        mEditorView.show(editor);
+    }
+
+    private EditorFieldValidator getPhoneValidator() {
+        if (mPhoneValidator == null) {
+            mPhoneValidator = new EditorFieldValidator() {
+                @Override
+                public boolean isValid(@Nullable CharSequence value) {
+                    return value != null
+                            && PhoneNumberUtils.isGlobalPhoneNumber(
+                                       PhoneNumberUtils.stripSeparators(value.toString()));
+                }
+            };
+        }
+        return mPhoneValidator;
+    }
+
+    private EditorFieldValidator getEmailValidator() {
+        if (mEmailValidator == null) {
+            mEmailValidator = new EditorFieldValidator() {
+                @Override
+                public boolean isValid(@Nullable CharSequence value) {
+                    return value != null && Patterns.EMAIL_ADDRESS.matcher(value).matches();
+                }
+            };
+        }
+        return mEmailValidator;
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index 99b9ae17..56f79dda 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -7,20 +7,14 @@
 import android.app.Activity;
 import android.graphics.Bitmap;
 import android.os.Handler;
-import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
-import android.util.Patterns;
 
 import org.chromium.base.Callback;
 import org.chromium.base.Log;
 import org.chromium.base.VisibleForTesting;
-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.favicon.FaviconHelper;
-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 org.chromium.chrome.browser.payments.ui.LineItem;
 import org.chromium.chrome.browser.payments.ui.PaymentInformation;
 import org.chromium.chrome.browser.payments.ui.PaymentOption;
@@ -135,12 +129,7 @@
     private Pattern mRegionCodePattern;
     private boolean mMerchantNeedsShippingAddress;
     private boolean mPaymentAppRunning;
-    private boolean mRequestPayerPhone;
-    private boolean mRequestPayerEmail;
-    private List<CharSequence> mAllPhoneNumbers;
-    private List<CharSequence> mAllEmailAddresses;
-    private EditorFieldValidator mPhoneValidator;
-    private EditorFieldValidator mEmailValidator;
+    private ContactEditor mContactEditor;
 
     /**
      * Builds the PaymentRequest service implementation.
@@ -227,30 +216,31 @@
         mMerchantNeedsShippingAddress =
                 requestShipping && mUiShippingOptions.getSelectedItem() == null;
 
-        mRequestPayerPhone = options != null && options.requestPayerPhone;
-        mRequestPayerEmail = options != null && options.requestPayerEmail;
+        boolean requestPayerPhone = options != null && options.requestPayerPhone;
+        boolean requestPayerEmail = options != null && options.requestPayerEmail;
+        if (requestPayerPhone || requestPayerEmail) {
+            mContactEditor = new ContactEditor(requestPayerPhone, requestPayerEmail);
+        }
 
-        if (requestShipping || mRequestPayerPhone || mRequestPayerEmail) {
+        if (requestShipping || requestPayerPhone || requestPayerEmail) {
             List<AutofillProfile> profiles =
                     PersonalDataManager.getInstance().getProfilesToSuggest();
             List<AutofillContact> contacts = new ArrayList<>();
             List<AutofillAddress> addresses = new ArrayList<>();
-            mAllPhoneNumbers = new ArrayList<>();
-            mAllEmailAddresses = new ArrayList<>();
             int firstCompleteContactIndex = SectionInformation.NO_SELECTION;
             for (int i = 0; i < profiles.size(); i++) {
                 AutofillProfile profile = profiles.get(i);
 
-                String phone = mRequestPayerPhone && !TextUtils.isEmpty(profile.getPhoneNumber())
+                String phone = requestPayerPhone && !TextUtils.isEmpty(profile.getPhoneNumber())
                         ? profile.getPhoneNumber() : null;
-                String email = mRequestPayerEmail && !TextUtils.isEmpty(profile.getEmailAddress())
+                String email = requestPayerEmail && !TextUtils.isEmpty(profile.getEmailAddress())
                         ? profile.getEmailAddress() : null;
                 if (phone != null || email != null) {
-                    boolean isComplete = isContactInformationComplete(phone, email);
+                    boolean isComplete = mContactEditor.isContactInformationComplete(phone, email);
                     contacts.add(new AutofillContact(profile, phone, email, isComplete));
                     if (isComplete && firstCompleteContactIndex < 0) firstCompleteContactIndex = i;
-                    if (getPhoneValidator().isValid(phone)) mAllPhoneNumbers.add(phone);
-                    if (getEmailValidator().isValid(email)) mAllEmailAddresses.add(email);
+                    mContactEditor.addPhoneNumberIfValid(phone);
+                    mContactEditor.addEmailAddressIfValid(email);
                 }
 
                 if (canUseAddress(profile, requestShipping)) {
@@ -268,7 +258,7 @@
             }
 
             // The contact section automatically selects the first complete entry.
-            if (mRequestPayerPhone || mRequestPayerEmail) {
+            if (requestPayerPhone || requestPayerEmail) {
                 mContactSection = new SectionInformation(
                         PaymentRequestUI.TYPE_CONTACT_DETAILS, firstCompleteContactIndex, contacts);
             }
@@ -295,9 +285,12 @@
         }
 
         mUI = new PaymentRequestUI(mContext, this, requestShipping,
-                mRequestPayerPhone || mRequestPayerEmail, mMerchantName, mOrigin);
+                requestPayerPhone || requestPayerEmail, mMerchantName, mOrigin);
+
         if (mFavicon != null) mUI.setTitleBitmap(mFavicon);
         mFavicon = null;
+
+        if (mContactEditor != null) mContactEditor.setEditorView(mUI.getEditorView());
     }
 
     private static HashMap<String, JSONObject> getValidatedMethodData(
@@ -335,11 +328,6 @@
         return result;
     }
 
-    private boolean isContactInformationComplete(String phone, String email) {
-        return (!mRequestPayerPhone || getPhoneValidator().isValid(phone))
-                && (!mRequestPayerEmail || getEmailValidator().isValid(email));
-    }
-
     private boolean canUseAddress(AutofillProfile profile, boolean requestShipping) {
         return requestShipping && profile.getCountryCode() != null
                 && mRegionCodePattern.matcher(profile.getCountryCode()).matches()
@@ -621,93 +609,6 @@
         return false;
     }
 
-    private void editContact(final AutofillContact contact) {
-        final EditorFieldModel phoneField = mRequestPayerPhone
-                ? new EditorFieldModel(EditorFieldModel.INPUT_TYPE_HINT_PHONE,
-                          mContext.getString(R.string.autofill_profile_editor_phone_number),
-                          mAllPhoneNumbers, getPhoneValidator(),
-                          mContext.getString(R.string.payments_phone_required_validation_message),
-                          mContext.getString(R.string.payments_phone_invalid_validation_message),
-                          contact == null ? null : contact.getPayerPhone())
-                : null;
-
-        final EditorFieldModel emailField = mRequestPayerEmail
-                ? new EditorFieldModel(EditorFieldModel.INPUT_TYPE_HINT_EMAIL,
-                          mContext.getString(R.string.autofill_profile_editor_email_address),
-                          mAllEmailAddresses, getEmailValidator(),
-                          mContext.getString(R.string.payments_email_required_validation_message),
-                          mContext.getString(R.string.payments_email_invalid_validation_message),
-                          contact == null ? null : contact.getPayerEmail())
-                : null;
-
-        EditorModel editor =
-                new EditorModel(mContext.getString(R.string.payments_add_contact_details_label));
-        if (phoneField != null) editor.addField(phoneField);
-        if (emailField != null) editor.addField(emailField);
-
-        editor.setCancelCallback(new Runnable() {
-            @Override
-            public void run() {
-                mContactSection.setSelectedItemIndex(SectionInformation.NO_SELECTION);
-                mUI.updateSection(PaymentRequestUI.TYPE_CONTACT_DETAILS, mContactSection);
-            }
-        });
-
-        editor.setDoneCallback(new Runnable() {
-            @Override
-            public void run() {
-                AutofillProfile profile =
-                        contact != null ? contact.getProfile() : new AutofillProfile();
-                String phone = null;
-                String email = null;
-                if (phoneField != null) {
-                    phone = phoneField.getValue().toString();
-                    profile.setPhoneNumber(phone);
-                }
-                if (emailField != null) {
-                    email = emailField.getValue().toString();
-                    profile.setEmailAddress(email);
-                }
-                PersonalDataManager.getInstance().setProfile(profile);
-
-                if (contact == null) {
-                    mContactSection.addAndSelectItem(
-                            new AutofillContact(profile, phone, email, true));
-                } else {
-                    contact.completeContact(phone, email);
-                }
-                mUI.updateSection(PaymentRequestUI.TYPE_CONTACT_DETAILS, mContactSection);
-            }
-        });
-        mUI.showEditor(editor);
-    }
-
-    private EditorFieldValidator getPhoneValidator() {
-        if (mPhoneValidator == null) {
-            mPhoneValidator = new EditorFieldValidator() {
-                @Override
-                public boolean isValid(CharSequence value) {
-                    return value != null
-                            && PhoneNumberUtils.isGlobalPhoneNumber(
-                                       PhoneNumberUtils.stripSeparators(value.toString()));
-                }
-            };
-        }
-        return mPhoneValidator;
-    }
-
-    private EditorFieldValidator getEmailValidator() {
-        if (mEmailValidator == null) {
-            mEmailValidator = new EditorFieldValidator() {
-                @Override
-                public boolean isValid(CharSequence value) {
-                    return value != null && Patterns.EMAIL_ADDRESS.matcher(value).matches();
-                }
-            };
-        }
-        return mEmailValidator;
-    }
-
     @Override
     public void onSectionAddOption(@PaymentRequestUI.DataType int optionType) {
         if (optionType == PaymentRequestUI.TYPE_SHIPPING_ADDRESSES) {
@@ -720,6 +621,21 @@
         }
     }
 
+    private void editContact(final AutofillContact toEdit) {
+        mContactEditor.editContact(toEdit, new Callback<AutofillContact>() {
+            @Override
+            public void onResult(AutofillContact completeContact) {
+                if (completeContact == null) {
+                    mContactSection.setSelectedItemIndex(SectionInformation.NO_SELECTION);
+                } else if (toEdit == null) {
+                    mContactSection.addAndSelectItem(completeContact);
+                }
+
+                mUI.updateSection(PaymentRequestUI.TYPE_CONTACT_DETAILS, mContactSection);
+            }
+        });
+    }
+
     @Override
     public void onPayClicked(PaymentOption selectedShippingAddress,
             PaymentOption selectedShippingOption, PaymentOption selectedPaymentMethod) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java
index c673af7..9996db1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/EditorView.java
@@ -334,6 +334,8 @@
         prepareToolbar();
         prepareButtons();
         prepareEditor();
+        getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT);
         show();
 
         // Immediately focus the first invalid field to make it faster to edit.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java
index ae91771..7e15a3a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestUI.java
@@ -582,13 +582,9 @@
         }
     }
 
-    /**
-     * Displays the editor user interface for the given model.
-     *
-     * @param editorModel The description of the editor user interface to display.
-     */
-    public void showEditor(final EditorModel editorModel) {
-        mEditorView.show(editorModel);
+    /** @return The editor user interface. */
+    public EditorView getEditorView() {
+        return mEditorView;
     }
 
     /**
@@ -1016,11 +1012,6 @@
     }
 
     @VisibleForTesting
-    public Dialog getEditorViewForTest() {
-        return mEditorView;
-    }
-
-    @VisibleForTesting
     public ViewGroup getShippingAddressSectionForTest() {
         return mShippingAddressSection;
     }
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index ad2972d..c62fd62 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -579,6 +579,7 @@
   "java/src/org/chromium/chrome/browser/payments/AutofillContact.java",
   "java/src/org/chromium/chrome/browser/payments/AutofillPaymentApp.java",
   "java/src/org/chromium/chrome/browser/payments/AutofillPaymentInstrument.java",
+  "java/src/org/chromium/chrome/browser/payments/ContactEditor.java",
   "java/src/org/chromium/chrome/browser/payments/CurrencyStringFormatter.java",
   "java/src/org/chromium/chrome/browser/payments/PaymentApp.java",
   "java/src/org/chromium/chrome/browser/payments/PaymentAppFactory.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestBase.java b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestBase.java
index 126a02a..e7149b74 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestBase.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/payments/PaymentRequestTestBase.java
@@ -140,7 +140,7 @@
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                mUI.getEditorViewForTest().findViewById(resourceId).performClick();
+                mUI.getEditorView().findViewById(resourceId).performClick();
             }
         });
         helper.waitForCallback(callCount);
@@ -176,7 +176,7 @@
         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
             @Override
             public void run() {
-                ((EditText) mUI.getEditorViewForTest().findViewById(resourceId)).setText(input);
+                ((EditText) mUI.getEditorView().findViewById(resourceId)).setText(input);
             }
         });
     }