Deduplicate Address class

Change-Id: I4f6df51f5641475ffaf96b0189ccc00748880cc0
diff --git a/proguard.flags b/proguard.flags
index 1ac96a5..3e3182d 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -77,3 +77,7 @@
 
 # not yet used in UnifiedEmail, but needed by UnifiedEmailTests
 -keep class com.android.mail.ui.MailAsyncTaskLoader
+
+-keepclasseswithmembers class com.android.emailcommon.mail.Address {
+  public <methods>;
+}
diff --git a/src/com/android/emailcommon/mail/Address.java b/src/com/android/emailcommon/mail/Address.java
index b648b81..ccd18a3 100644
--- a/src/com/android/emailcommon/mail/Address.java
+++ b/src/com/android/emailcommon/mail/Address.java
@@ -13,13 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.emailcommon.mail;
 
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.Html;
 import android.text.TextUtils;
 import android.text.util.Rfc822Token;
 import android.text.util.Rfc822Tokenizer;
 
+import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
 import com.google.common.annotations.VisibleForTesting;
 
 import org.apache.james.mime4j.codec.EncoderUtil;
@@ -39,7 +43,8 @@
  * Name and comment part should be MIME/base64 encoded in header if necessary.
  *
  */
-public class Address {
+public class Address implements Parcelable {
+    public static final String ADDRESS_DELIMETER = ",";
     /**
      *  Address part, in the form local_part@domain_part. No surrounding angle brackets.
      */
@@ -51,6 +56,12 @@
      */
     private String mPersonal;
 
+    /**
+     * When personal is set, it will return the first token of the personal
+     * string. Otherwise, it will return the e-mail address up to the '@' sign.
+     */
+    private String mSimplifiedName;
+
     // Regex that matches address surrounded by '<>' optionally. '^<?([^>]+)>?$'
     private static final Pattern REMOVE_OPTIONAL_BRACKET = Pattern.compile("^<?([^>]+)>?$");
     // Regex that matches personal name surrounded by '""' optionally. '^"?([^"]+)"?$'
@@ -58,7 +69,6 @@
     // Regex that matches escaped character '\\([\\"])'
     private static final Pattern UNQUOTE = Pattern.compile("\\\\([\\\\\"])");
 
-
     // TODO: LOCAL_PART and DOMAIN_PART_PART are too permissive and can be improved.
     // TODO: Fix this to better constrain comments.
     /** Regex for the local part of an email address. */
@@ -79,15 +89,64 @@
     private static final char LIST_DELIMITER_EMAIL = '\1';
     private static final char LIST_DELIMITER_PERSONAL = '\2';
 
-    public Address(String address, String personal) {
-        setAddress(address);
-        setPersonal(personal);
-    }
+    private static final String LOG_TAG = LogTag.getLogTag();
 
     public Address(String address) {
         setAddress(address);
     }
 
+    public Address(String address, String personal) {
+        setPersonal(personal);
+        setAddress(address);
+    }
+
+    /**
+     * Returns a simplified string for this e-mail address.
+     * When a name is known, it will return the first token of that name. Otherwise, it will
+     * return the e-mail address up to the '@' sign.
+     */
+    public String getSimplifiedName() {
+        if (mSimplifiedName == null) {
+            if (TextUtils.isEmpty(mPersonal) && !TextUtils.isEmpty(mAddress)) {
+                int atSign = mAddress.indexOf('@');
+                mSimplifiedName = (atSign != -1) ? mAddress.substring(0, atSign) : "";
+            } else if (!TextUtils.isEmpty(mPersonal)) {
+
+                // TODO: use Contacts' NameSplitter for more reliable first-name extraction
+
+                int end = mPersonal.indexOf(' ');
+                while (end > 0 && mPersonal.charAt(end - 1) == ',') {
+                    end--;
+                }
+                mSimplifiedName = (end < 1) ? mPersonal : mPersonal.substring(0, end);
+
+            } else {
+                LogUtils.w(LOG_TAG, "Unable to get a simplified name");
+                mSimplifiedName = "";
+            }
+        }
+        return mSimplifiedName;
+    }
+
+    public static synchronized Address getEmailAddress(String rawAddress) {
+        if (TextUtils.isEmpty(rawAddress)) {
+            return null;
+        }
+        String name, address;
+        final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(rawAddress);
+        if (tokens.length > 0) {
+            final String tokenizedName = tokens[0].getName();
+            name = tokenizedName != null ? Html.fromHtml(tokenizedName.trim()).toString()
+                    : "";
+            address = Html.fromHtml(tokens[0].getAddress()).toString();
+        } else {
+            name = "";
+            address = rawAddress == null ?
+                    "" : Html.fromHtml(rawAddress).toString();
+        }
+        return new Address(address, name);
+    }
+
     public String getAddress() {
         return mAddress;
     }
@@ -106,12 +165,22 @@
     }
 
     /**
-     * Set name part from UTF-16 string. Optional surrounding double quote will be removed.
+     * Set personal part from UTF-16 string. Optional surrounding double quote will be removed.
      * It will be also unquoted and MIME/base64 decoded.
      *
      * @param personal name part of email address as UTF-16 string. Null is acceptable.
      */
     public void setPersonal(String personal) {
+        mPersonal = decodeAddressPersonal(personal);
+    }
+
+    /**
+     * Decodes name from UTF-16 string. Optional surrounding double quote will be removed.
+     * It will be also unquoted and MIME/base64 decoded.
+     *
+     * @param personal name part of email address as UTF-16 string. Null is acceptable.
+     */
+    public static String decodeAddressPersonal(String personal) {
         if (personal != null) {
             personal = REMOVE_OPTIONAL_DQUOTE.matcher(personal).replaceAll("$1");
             personal = UNQUOTE.matcher(personal).replaceAll("$1");
@@ -120,7 +189,7 @@
                 personal = null;
             }
         }
-        mPersonal = personal;
+        return personal;
     }
 
     /**
@@ -169,7 +238,7 @@
                 }
             }
         }
-        return addresses.toArray(new Address[] {});
+        return addresses.toArray(new Address[addresses.size()]);
     }
 
     /**
@@ -212,7 +281,7 @@
     public String toString() {
         if (mPersonal != null && !mPersonal.equals(mAddress)) {
             if (mPersonal.matches(".*[\\(\\)<>@,;:\\\\\".\\[\\]].*")) {
-                return quoteString(mPersonal) + " <" + mAddress + ">";
+                return ensureQuotedString(mPersonal) + " <" + mAddress + ">";
             } else {
                 return mPersonal + " <" + mAddress + ">";
             }
@@ -226,8 +295,6 @@
      * not modified in any way except to add the double quote character to start and end if it's not
      * already there.
      *
-     * TODO: Rename this, because "quoteString()" can mean so many different things.
-     *
      * sample -> "sample"
      * "sample" -> "sample"
      * ""sample"" -> "sample"
@@ -237,14 +304,13 @@
      * (empty string) -> ""
      * " -> ""
      */
-    public static String quoteString(String s) {
+    private static String ensureQuotedString(String s) {
         if (s == null) {
             return null;
         }
         if (!s.matches("^\".*\"$")) {
             return "\"" + s + "\"";
-        }
-        else {
+        } else {
             return s;
         }
     }
@@ -256,7 +322,7 @@
      * @return Human readable comma-delimited address string.
      */
     public static String toString(Address[] addresses) {
-        return toString(addresses, ",");
+        return toString(addresses, ADDRESS_DELIMETER);
     }
 
     /**
@@ -273,7 +339,7 @@
         if (addresses.length == 1) {
             return addresses[0].toString();
         }
-        StringBuffer sb = new StringBuffer(addresses[0].toString());
+        StringBuilder sb = new StringBuilder(addresses[0].toString());
         for (int i = 1; i < addresses.length; i++) {
             sb.append(separator);
             // TODO: investigate why this .trim() is needed.
@@ -310,7 +376,7 @@
         if (addresses.length == 1) {
             return addresses[0].toHeader();
         }
-        StringBuffer sb = new StringBuffer(addresses[0].toHeader());
+        StringBuilder sb = new StringBuilder(addresses[0].toHeader());
         for (int i = 1; i < addresses.length; i++) {
             // We need space character to be able to fold line.
             sb.append(", ");
@@ -348,7 +414,7 @@
         if (addresses.length == 1) {
             return addresses[0].toFriendly();
         }
-        StringBuffer sb = new StringBuffer(addresses[0].toFriendly());
+        StringBuilder sb = new StringBuilder(addresses[0].toFriendly());
         for (int i = 1; i < addresses.length; i++) {
             sb.append(", ");
             sb.append(addresses[i].toFriendly());
@@ -404,11 +470,11 @@
                 (addressList.indexOf(LIST_DELIMITER_EMAIL) == -1)) {
             return Address.parse(addressList);
         }
-        // Otherwise, do backward-compatibile unpack
+        // Otherwise, do backward-compatible unpack
         ArrayList<Address> addresses = new ArrayList<Address>();
         int length = addressList.length();
         int pairStartIndex = 0;
-        int pairEndIndex = 0;
+        int pairEndIndex;
 
         /* addressEndIndex is only re-scanned (indexOf()) when a LIST_DELIMITER_PERSONAL
            is used, not for every email address; i.e. not for every iteration of the while().
@@ -429,14 +495,14 @@
                 address = new Address(addressList.substring(pairStartIndex, pairEndIndex), null);
             } else {
                 address = new Address(addressList.substring(pairStartIndex, addressEndIndex),
-                                      addressList.substring(addressEndIndex + 1, pairEndIndex));
+                        addressList.substring(addressEndIndex + 1, pairEndIndex));
                 // only update addressEndIndex when we use the LIST_DELIMITER_PERSONAL
                 addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL, pairEndIndex + 1);
             }
             addresses.add(address);
             pairStartIndex = pairEndIndex + 1;
         }
-        return addresses.toArray(EMPTY_ADDRESS_ARRAY);
+        return addresses.toArray(new Address[addresses.size()]);
     }
 
     /**
@@ -451,12 +517,34 @@
      * Produces the same result as pack(array), but only packs one (this) address.
      */
     public String pack() {
-        final String address = getAddress();
-        final String personal = getPersonal();
-        if (personal == null) {
-            return address;
-        } else {
-            return address + LIST_DELIMITER_PERSONAL + personal;
+        return toHeader();
+    }
+
+    public static final Creator<Address> CREATOR = new Creator<Address>() {
+        @Override
+        public Address createFromParcel(Parcel parcel) {
+            return new Address(parcel);
         }
+
+        @Override
+        public Address[] newArray(int size) {
+            return new Address[size];
+        }
+    };
+
+    public Address(Parcel in) {
+        setPersonal(in.readString());
+        setAddress(in.readString());
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mPersonal);
+        out.writeString(mAddress);
     }
 }
diff --git a/src/com/android/mail/browse/ConversationItemView.java b/src/com/android/mail/browse/ConversationItemView.java
index 464ce5a..6d42e79 100644
--- a/src/com/android/mail/browse/ConversationItemView.java
+++ b/src/com/android/mail/browse/ConversationItemView.java
@@ -71,7 +71,6 @@
 import com.android.mail.bitmap.ContactDrawable;
 import com.android.mail.browse.ConversationItemViewModel.SenderFragment;
 import com.android.mail.perf.Timer;
-import com.android.mail.providers.Address;
 import com.android.mail.providers.Attachment;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
diff --git a/src/com/android/mail/browse/ConversationViewAdapter.java b/src/com/android/mail/browse/ConversationViewAdapter.java
index 8406531..5dc55eb 100644
--- a/src/com/android/mail/browse/ConversationViewAdapter.java
+++ b/src/com/android/mail/browse/ConversationViewAdapter.java
@@ -26,13 +26,13 @@
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
 
+import com.android.emailcommon.mail.Address;
 import com.android.mail.ContactInfoSource;
 import com.android.mail.FormattedDateBuilder;
 import com.android.mail.R;
 import com.android.mail.browse.ConversationViewHeader.ConversationViewHeaderCallbacks;
 import com.android.mail.browse.MessageHeaderView.MessageHeaderViewCallbacks;
 import com.android.mail.browse.SuperCollapsedBlock.OnClickListener;
-import com.android.mail.providers.Address;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.ui.ControllableActivity;
diff --git a/src/com/android/mail/browse/EmlMessageViewFragment.java b/src/com/android/mail/browse/EmlMessageViewFragment.java
index 04eabd6..8549303 100644
--- a/src/com/android/mail/browse/EmlMessageViewFragment.java
+++ b/src/com/android/mail/browse/EmlMessageViewFragment.java
@@ -32,9 +32,9 @@
 import android.view.ViewGroup;
 import android.webkit.WebView;
 
+import com.android.emailcommon.mail.Address;
 import com.android.mail.R;
 import com.android.mail.providers.Account;
-import com.android.mail.providers.Address;
 import com.android.mail.ui.AbstractConversationWebViewClient;
 import com.android.mail.ui.ContactLoaderCallbacks;
 import com.android.mail.ui.SecureConversationViewController;
diff --git a/src/com/android/mail/browse/MessageHeaderView.java b/src/com/android/mail/browse/MessageHeaderView.java
index 48d78c5..5659527 100644
--- a/src/com/android/mail/browse/MessageHeaderView.java
+++ b/src/com/android/mail/browse/MessageHeaderView.java
@@ -44,6 +44,7 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import com.android.emailcommon.mail.Address;
 import com.android.mail.ContactInfo;
 import com.android.mail.ContactInfoSource;
 import com.android.mail.R;
@@ -55,7 +56,6 @@
 import com.android.mail.photomanager.LetterTileProvider;
 import com.android.mail.print.PrintUtils;
 import com.android.mail.providers.Account;
-import com.android.mail.providers.Address;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
 import com.android.mail.providers.Message;
@@ -562,7 +562,7 @@
         if (sender == null) {
             return "";
         }
-        final String displayName = sender.getName();
+        final String displayName = sender.getPersonal();
         return TextUtils.isEmpty(displayName) ? sender.getAddress() : displayName;
     }
 
@@ -753,7 +753,7 @@
                 final String emailAddress = email.getAddress();
                 final String name;
                 if (mMatcher != null && mMatcher.isVeiledAddress(emailAddress)) {
-                    if (TextUtils.isEmpty(email.getName())) {
+                    if (TextUtils.isEmpty(email.getPersonal())) {
                         // Let's write something more readable.
                         name = mContext.getString(VeiledAddressMatcher.VEILED_SUMMARY_UNKNOWN);
                     } else {
@@ -809,7 +809,9 @@
         // and ensure either the contact URI or email is set so the click
         // handling works
         String contentDesc = getResources().getString(R.string.contact_info_string,
-                !TextUtils.isEmpty(mSender.getName()) ? mSender.getName() : mSender.getAddress());
+                !TextUtils.isEmpty(mSender.getPersonal())
+                        ? mSender.getPersonal()
+                        : mSender.getAddress());
         mPhotoView.setContentDescription(contentDesc);
         boolean photoSet = false;
         final String email = mSender.getAddress();
@@ -825,7 +827,7 @@
         }
 
         if (!photoSet) {
-            mPhotoView.setImageBitmap(makeLetterTile(mSender.getName(), email));
+            mPhotoView.setImageBitmap(makeLetterTile(mSender.getPersonal(), email));
         }
     }
 
@@ -1341,7 +1343,7 @@
         final String[] formattedEmails = new String[emails.length];
         for (int i = 0; i < emails.length; i++) {
             final Address email = Utils.getAddress(addressCache, emails[i]);
-            String name = email.getName();
+            String name = email.getPersonal();
             final String address = email.getAddress();
             // Check if the address here is a veiled address.  If it is, we need to display an
             // alternate layout
diff --git a/src/com/android/mail/browse/SendersView.java b/src/com/android/mail/browse/SendersView.java
index bafa532..8d8240f 100644
--- a/src/com/android/mail/browse/SendersView.java
+++ b/src/com/android/mail/browse/SendersView.java
@@ -33,8 +33,8 @@
 import android.text.util.Rfc822Token;
 import android.text.util.Rfc822Tokenizer;
 
+import com.android.emailcommon.mail.Address;
 import com.android.mail.R;
-import com.android.mail.providers.Address;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.ConversationInfo;
 import com.android.mail.providers.MessageInfo;
@@ -399,7 +399,7 @@
             final String[] namesOnly = new String[senders.length];
             String display;
             for (int i = 0; i < senders.length; i++) {
-                display = Address.decodeAddressName(senders[i].getName());
+                display = Address.decodeAddressPersonal(senders[i].getName());
                 if (TextUtils.isEmpty(display)) {
                     display = senders[i].getAddress();
                 }
diff --git a/src/com/android/mail/browse/SnapHeader.java b/src/com/android/mail/browse/SnapHeader.java
index 9428a9f..bd8a026 100644
--- a/src/com/android/mail/browse/SnapHeader.java
+++ b/src/com/android/mail/browse/SnapHeader.java
@@ -20,9 +20,9 @@
 import android.util.AttributeSet;
 import android.widget.LinearLayout;
 
+import com.android.emailcommon.mail.Address;
 import com.android.mail.ContactInfoSource;
 import com.android.mail.browse.MessageHeaderView.MessageHeaderViewCallbacks;
-import com.android.mail.providers.Address;
 import com.android.mail.utils.VeiledAddressMatcher;
 
 import java.util.Map;
diff --git a/src/com/android/mail/browse/SpamWarningView.java b/src/com/android/mail/browse/SpamWarningView.java
index 1dbc2b3..598d109 100644
--- a/src/com/android/mail/browse/SpamWarningView.java
+++ b/src/com/android/mail/browse/SpamWarningView.java
@@ -8,8 +8,8 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import com.android.emailcommon.mail.Address;
 import com.android.mail.R;
-import com.android.mail.providers.Address;
 import com.android.mail.providers.Message;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.utils.Utils;
diff --git a/src/com/android/mail/compose/ComposeActivity.java b/src/com/android/mail/compose/ComposeActivity.java
index 0b714a5..c507161 100644
--- a/src/com/android/mail/compose/ComposeActivity.java
+++ b/src/com/android/mail/compose/ComposeActivity.java
@@ -72,6 +72,7 @@
 
 import com.android.common.Rfc822Validator;
 import com.android.common.contacts.DataUsageStatUpdater;
+import com.android.emailcommon.mail.Address;
 import com.android.ex.chips.RecipientEditTextView;
 import com.android.mail.MailIntentService;
 import com.android.mail.R;
@@ -82,7 +83,6 @@
 import com.android.mail.compose.FromAddressSpinner.OnAccountChangedListener;
 import com.android.mail.compose.QuotedTextView.RespondInlineListener;
 import com.android.mail.providers.Account;
-import com.android.mail.providers.Address;
 import com.android.mail.providers.Attachment;
 import com.android.mail.providers.Folder;
 import com.android.mail.providers.MailAppProvider;
@@ -950,7 +950,7 @@
                 : mAccount != null ? mAccount.getEmailAddress() : null;
         final String senderName = selectedReplyFromAccount != null ? selectedReplyFromAccount.name
                 : mAccount != null ? mAccount.getSenderName() : null;
-        final Address address = new Address(senderName, email);
+        final Address address = new Address(email, senderName);
         message.setFrom(address.toHeader());
         message.draftType = getDraftType(mode);
         return message;
diff --git a/src/com/android/mail/print/PrintUtils.java b/src/com/android/mail/print/PrintUtils.java
index 2d387c3..86210db 100644
--- a/src/com/android/mail/print/PrintUtils.java
+++ b/src/com/android/mail/print/PrintUtils.java
@@ -26,11 +26,11 @@
 import android.webkit.WebSettings;
 import android.webkit.WebView;
 
+import com.android.emailcommon.mail.Address;
 import com.android.mail.FormattedDateBuilder;
 import com.android.mail.R;
 import com.android.mail.browse.MessageCursor;
 
-import com.android.mail.providers.Address;
 import com.android.mail.providers.Attachment;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Message;
@@ -160,7 +160,7 @@
         final long when = message.dateReceivedMs;
         final String date = dateBuilder.formatDateTimeForPrinting(when);
 
-        templates.appendMessage(fromAddress.getName(), fromAddress.getAddress(), date,
+        templates.appendMessage(fromAddress.getPersonal(), fromAddress.getAddress(), date,
                 renderRecipients(res, addressCache, message), message.getBodyAsHtml(),
                 renderAttachments(context, res, message));
     }
@@ -229,7 +229,7 @@
         final String[] formattedEmails = new String[emails.length];
         for (int i = 0; i < emails.length; i++) {
             final Address email = Utils.getAddress(addressCache, emails[i]);
-            final String name = email.getName();
+            final String name = email.getPersonal();
             final String address = email.getAddress();
 
             if (TextUtils.isEmpty(name)) {
diff --git a/src/com/android/mail/providers/Address.java b/src/com/android/mail/providers/Address.java
deleted file mode 100644
index edd2db1..0000000
--- a/src/com/android/mail/providers/Address.java
+++ /dev/null
@@ -1,547 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.mail.providers;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-import android.text.util.Rfc822Token;
-import android.text.util.Rfc822Tokenizer;
-
-import com.android.common.Rfc822Validator;
-import com.android.mail.utils.LogTag;
-import com.android.mail.utils.LogUtils;
-import com.android.mail.utils.Utils;
-import com.google.common.annotations.VisibleForTesting;
-
-import org.apache.james.mime4j.codec.EncoderUtil;
-import org.apache.james.mime4j.decoder.DecoderUtil;
-
-import java.util.ArrayList;
-import java.util.regex.Pattern;
-
-/**
- * This class represent email address.
- *
- * RFC822 email address may have following format.
- *   "name" <address> (comment)
- *   "name" <address>
- *   name <address>
- *   address
- * Name and comment part should be MIME/base64 encoded in header if necessary.
- *
- */
-public class Address implements Parcelable {
-    public static final String ADDRESS_DELIMETER = ",";
-    /**
-     *  Address part, in the form local_part@domain_part. No surrounding angle brackets.
-     */
-    private String mAddress;
-
-    /**
-     * Name part. No surrounding double quote, and no MIME/base64 encoding.
-     * This must be null if Address has no name part.
-     */
-    private String mName;
-
-    /**
-     * When personal is set, it will return the first token of the personal
-     * string. Otherwise, it will return the e-mail address up to the '@' sign.
-     */
-    private String mSimplifiedName;
-
-    // Regex that matches address surrounded by '<>' optionally. '^<?([^>]+)>?$'
-    private static final Pattern REMOVE_OPTIONAL_BRACKET = Pattern.compile("^<?([^>]+)>?$");
-    // Regex that matches personal name surrounded by '""' optionally. '^"?([^"]+)"?$'
-    private static final Pattern REMOVE_OPTIONAL_DQUOTE = Pattern.compile("^\"?([^\"]*)\"?$");
-    // Regex that matches escaped character '\\([\\"])'
-    private static final Pattern UNQUOTE = Pattern.compile("\\\\([\\\\\"])");
-
-    private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0];
-
-    // delimiters are chars that do not appear in an email address, used by pack/unpack
-    private static final char LIST_DELIMITER_EMAIL = '\1';
-    private static final char LIST_DELIMITER_PERSONAL = '\2';
-
-    private static final String LOG_TAG = LogTag.getLogTag();
-
-    public Address(String name, String address) {
-        setName(name);
-        setAddress(address);
-    }
-
-
-
-    /**
-     * Returns a simplified string for this e-mail address.
-     * When a name is known, it will return the first token of that name. Otherwise, it will
-     * return the e-mail address up to the '@' sign.
-     */
-    public String getSimplifiedName() {
-        if (mSimplifiedName == null) {
-            if (TextUtils.isEmpty(mName) && !TextUtils.isEmpty(mAddress)) {
-                int atSign = mAddress.indexOf('@');
-                mSimplifiedName = (atSign != -1) ? mAddress.substring(0, atSign) : "";
-            } else if (!TextUtils.isEmpty(mName)) {
-
-                // TODO: use Contacts' NameSplitter for more reliable first-name extraction
-
-                int end = mName.indexOf(' ');
-                while (end > 0 && mName.charAt(end - 1) == ',') {
-                    end--;
-                }
-                mSimplifiedName = (end < 1) ? mName : mName.substring(0, end);
-
-            } else {
-                LogUtils.w(LOG_TAG, "Unable to get a simplified name");
-                mSimplifiedName = "";
-            }
-        }
-        return mSimplifiedName;
-    }
-
-    public static synchronized Address getEmailAddress(String rawAddress) {
-        if (TextUtils.isEmpty(rawAddress)) {
-            return null;
-        }
-        String name, address;
-        final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(rawAddress);
-        if (tokens.length > 0) {
-            final String tokenizedName = tokens[0].getName();
-            name = tokenizedName != null ? Utils.convertHtmlToPlainText(tokenizedName.trim())
-                    .toString() : "";
-            address = Utils.convertHtmlToPlainText(tokens[0].getAddress()).toString();
-        } else {
-            name = "";
-            address = rawAddress == null ?
-                    "" : Utils.convertHtmlToPlainText(rawAddress).toString();
-        }
-        return new Address(name, address);
-    }
-
-    public Address(String address) {
-        setAddress(address);
-    }
-
-    public String getAddress() {
-        return mAddress;
-    }
-
-    public void setAddress(String address) {
-        mAddress = REMOVE_OPTIONAL_BRACKET.matcher(address).replaceAll("$1");
-    }
-
-    /**
-     * Get name part as UTF-16 string. No surrounding double quote, and no MIME/base64 encoding.
-     *
-     * @return Name part of email address. Returns null if it is omitted.
-     */
-    public String getName() {
-        return mName;
-    }
-
-    /**
-     * Set name part from UTF-16 string. Optional surrounding double quote will be removed.
-     * It will be also unquoted and MIME/base64 decoded.
-     *
-     * @param name name part of email address as UTF-16 string. Null is acceptable.
-     */
-    public void setName(String name) {
-        mName = decodeAddressName(name);
-    }
-
-    /**
-     * Decodes name from UTF-16 string. Optional surrounding double quote will be removed.
-     * It will be also unquoted and MIME/base64 decoded.
-     *
-     * @param name name part of email address as UTF-16 string. Null is acceptable.
-     */
-    public static String decodeAddressName(String name) {
-        if (name != null) {
-            name = REMOVE_OPTIONAL_DQUOTE.matcher(name).replaceAll("$1");
-            name = UNQUOTE.matcher(name).replaceAll("$1");
-            name = DecoderUtil.decodeEncodedWords(name);
-            if (name.length() == 0) {
-                name = null;
-            }
-        }
-        return name;
-    }
-
-    /**
-     * This method is used to check that all the addresses that the user
-     * entered in a list (e.g. To:) are valid, so that none is dropped.
-     */
-    public static boolean isAllValid(String addressList) {
-        // This code mimics the parse() method below.
-        // I don't know how to better avoid the code-duplication.
-        if (addressList != null && addressList.length() > 0) {
-            Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
-            for (int i = 0, length = tokens.length; i < length; ++i) {
-                Rfc822Token token = tokens[i];
-                String address = token.getAddress();
-                if (!TextUtils.isEmpty(address) && !isValidAddress(address)) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Parse a comma-delimited list of addresses in RFC822 format and return an
-     * array of Address objects.
-     *
-     * @param addressList Address list in comma-delimited string.
-     * @return An array of 0 or more Addresses.
-     */
-    public static Address[] parse(String addressList) {
-        if (addressList == null || addressList.length() == 0) {
-            return EMPTY_ADDRESS_ARRAY;
-        }
-        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList);
-        ArrayList<Address> addresses = new ArrayList<Address>();
-        for (int i = 0, length = tokens.length; i < length; ++i) {
-            Rfc822Token token = tokens[i];
-            String address = token.getAddress();
-            if (!TextUtils.isEmpty(address)) {
-                if (isValidAddress(address)) {
-                    String name = token.getName();
-                    if (TextUtils.isEmpty(name)) {
-                        name = null;
-                    }
-                    addresses.add(new Address(name, address));
-                }
-            }
-        }
-        return addresses.toArray(new Address[] {});
-    }
-
-    /**
-     * Checks whether a string email address is valid.
-     * E.g. name@domain.com is valid.
-     */
-    @VisibleForTesting
-    static boolean isValidAddress(String address) {
-        if (TextUtils.isEmpty(address)) {
-            return false;
-        }
-        int index = address.indexOf("@");
-        return index == -1 ? false : new Rfc822Validator(address.substring(0, index))
-                .isValid(address);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o instanceof Address) {
-            // It seems that the spec says that the "user" part is case-sensitive,
-            // while the domain part in case-insesitive.
-            // So foo@yahoo.com and Foo@yahoo.com are different.
-            // This may seem non-intuitive from the user POV, so we
-            // may re-consider it if it creates UI trouble.
-            // A problem case is "replyAll" sending to both
-            // a@b.c and to A@b.c, which turn out to be the same on the server.
-            // Leave unchanged for now (i.e. case-sensitive).
-            return getAddress().equals(((Address) o).getAddress());
-        }
-        return super.equals(o);
-    }
-
-    /**
-     * Get human readable address string.
-     * Do not use this for email header.
-     *
-     * @return Human readable address string.  Not quoted and not encoded.
-     */
-    @Override
-    public String toString() {
-        if (mName != null && !mName.equals(mAddress)) {
-            if (mName.matches(".*[\\(\\)<>@,;:\\\\\".\\[\\]].*")) {
-                return Utils.ensureQuotedString(mName) + " <" + mAddress + ">";
-            } else {
-                return mName + " <" + mAddress + ">";
-            }
-        } else {
-            return mAddress;
-        }
-    }
-
-    /**
-     * Get human readable comma-delimited address string.
-     *
-     * @param addresses Address array
-     * @return Human readable comma-delimited address string.
-     */
-    public static String toString(Address[] addresses) {
-        return toString(addresses, ADDRESS_DELIMETER);
-    }
-
-    /**
-     * Get human readable address strings joined with the specified separator.
-     *
-     * @param addresses Address array
-     * @param separator Separator
-     * @return Human readable comma-delimited address string.
-     */
-    public static String toString(Address[] addresses, String separator) {
-        if (addresses == null || addresses.length == 0) {
-            return null;
-        }
-        if (addresses.length == 1) {
-            return addresses[0].toString();
-        }
-        StringBuffer sb = new StringBuffer(addresses[0].toString());
-        for (int i = 1; i < addresses.length; i++) {
-            sb.append(separator);
-            // TODO: investigate why this .trim() is needed.
-            sb.append(addresses[i].toString().trim());
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Get RFC822/MIME compatible address string.
-     *
-     * @return RFC822/MIME compatible address string.
-     * It may be surrounded by double quote or quoted and MIME/base64 encoded if necessary.
-     */
-    public String toHeader() {
-        if (mName != null) {
-            return EncoderUtil.encodeAddressDisplayName(mName) + " <" + mAddress + ">";
-        } else {
-            return mAddress;
-        }
-    }
-
-    /**
-     * Get RFC822/MIME compatible comma-delimited address string.
-     *
-     * @param addresses Address array
-     * @return RFC822/MIME compatible comma-delimited address string.
-     * it may be surrounded by double quoted or quoted and MIME/base64 encoded if necessary.
-     */
-    public static String toHeader(Address[] addresses) {
-        if (addresses == null || addresses.length == 0) {
-            return null;
-        }
-        if (addresses.length == 1) {
-            return addresses[0].toHeader();
-        }
-        StringBuffer sb = new StringBuffer(addresses[0].toHeader());
-        for (int i = 1; i < addresses.length; i++) {
-            // We need space character to be able to fold line.
-            sb.append(", ");
-            sb.append(addresses[i].toHeader());
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Get Human friendly address string.
-     *
-     * @return the personal part of this Address, or the address part if the
-     * personal part is not available
-     */
-    public String toFriendly() {
-        if (mName != null && mName.length() > 0) {
-            return mName;
-        } else {
-            return mAddress;
-        }
-    }
-
-    /**
-     * Creates a comma-delimited list of addresses in the "friendly" format (see toFriendly() for
-     * details on the per-address conversion).
-     *
-     * @param addresses Array of Address[] values
-     * @return A comma-delimited string listing all of the addresses supplied.  Null if source
-     * was null or empty.
-     */
-    public static String toFriendly(Address[] addresses) {
-        if (addresses == null || addresses.length == 0) {
-            return null;
-        }
-        if (addresses.length == 1) {
-            return addresses[0].toFriendly();
-        }
-        StringBuffer sb = new StringBuffer(addresses[0].toFriendly());
-        for (int i = 1; i < addresses.length; i++) {
-            sb.append(", ");
-            sb.append(addresses[i].toFriendly());
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Returns exactly the same result as Address.toString(Address.unpack(packedList)).
-     */
-    public static String unpackToString(String packedList) {
-        return toString(unpack(packedList));
-    }
-
-    /**
-     * Returns exactly the same result as Address.pack(Address.parse(textList)).
-     */
-    public static String parseAndPack(String textList) {
-        return Address.pack(Address.parse(textList));
-    }
-
-    /**
-     * Returns null if the packedList has 0 addresses, otherwise returns the first address.
-     * The same as Address.unpack(packedList)[0] for non-empty list.
-     * This is an utility method that offers some performance optimization opportunities.
-     */
-    public static Address unpackFirst(String packedList) {
-        Address[] array = unpack(packedList);
-        return array.length > 0 ? array[0] : null;
-    }
-
-    /**
-     * Convert a packed list of addresses to a form suitable for use in an RFC822 header.
-     * This implementation is brute-force, and could be replaced with a more efficient version
-     * if desired.
-     */
-    public static String packedToHeader(String packedList) {
-        return toHeader(unpack(packedList));
-    }
-
-    /**
-     * Unpacks an address list previously packed with pack()
-     * @param addressList String with packed addresses as returned by pack()
-     * @return array of addresses resulting from unpack
-     */
-    public static Address[] unpack(String addressList) {
-        if (addressList == null || addressList.length() == 0) {
-            return EMPTY_ADDRESS_ARRAY;
-        }
-        ArrayList<Address> addresses = new ArrayList<Address>();
-        int length = addressList.length();
-        int pairStartIndex = 0;
-        int pairEndIndex = 0;
-
-        /* addressEndIndex is only re-scanned (indexOf()) when a LIST_DELIMITER_PERSONAL
-           is used, not for every email address; i.e. not for every iteration of the while().
-           This reduces the theoretical complexity from quadratic to linear,
-           and provides some speed-up in practice by removing redundant scans of the string.
-        */
-        int addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL);
-
-        while (pairStartIndex < length) {
-            pairEndIndex = addressList.indexOf(LIST_DELIMITER_EMAIL, pairStartIndex);
-            if (pairEndIndex == -1) {
-                pairEndIndex = length;
-            }
-            Address address;
-            if (addressEndIndex == -1 || pairEndIndex <= addressEndIndex) {
-                // in this case the DELIMITER_PERSONAL is in a future pair,
-                // so don't use personal, and don't update addressEndIndex
-                address = new Address(null, addressList.substring(pairStartIndex, pairEndIndex));
-            } else {
-                address = new Address(addressList.substring(addressEndIndex + 1, pairEndIndex),
-                        addressList.substring(pairStartIndex, addressEndIndex));
-                // only update addressEndIndex when we use the LIST_DELIMITER_PERSONAL
-                addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL, pairEndIndex + 1);
-            }
-            addresses.add(address);
-            pairStartIndex = pairEndIndex + 1;
-        }
-        return addresses.toArray(EMPTY_ADDRESS_ARRAY);
-    }
-
-    /**
-     * Packs an address list into a String that is very quick to read
-     * and parse. Packed lists can be unpacked with unpack().
-     * The format is a series of packed addresses separated by LIST_DELIMITER_EMAIL.
-     * Each address is packed as
-     * a pair of address and personal separated by LIST_DELIMITER_PERSONAL,
-     * where the personal and delimiter are optional.
-     * E.g. "foo@x.com\1joe@x.com\2Joe Doe"
-     * @param addresses Array of addresses
-     * @return a string containing the packed addresses.
-     */
-    public static String pack(Address[] addresses) {
-        // TODO: return same value for both null & empty list
-        if (addresses == null) {
-            return null;
-        }
-        final int nAddr = addresses.length;
-        if (nAddr == 0) {
-            return "";
-        }
-
-        // shortcut: one email with no displayName
-        if (nAddr == 1 && addresses[0].getName() == null) {
-            return addresses[0].getAddress();
-        }
-
-        StringBuffer sb = new StringBuffer();
-        for (int i = 0; i < nAddr; i++) {
-            if (i != 0) {
-                sb.append(LIST_DELIMITER_EMAIL);
-            }
-            final Address address = addresses[i];
-            sb.append(address.getAddress());
-            final String displayName = address.getName();
-            if (displayName != null) {
-                sb.append(LIST_DELIMITER_PERSONAL);
-                sb.append(displayName);
-            }
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Produces the same result as pack(array), but only packs one (this) address.
-     */
-    public String pack() {
-        final String address = getAddress();
-        final String personal = getName();
-        if (personal == null) {
-            return address;
-        } else {
-            return address + LIST_DELIMITER_PERSONAL + personal;
-        }
-    }
-
-    public static final Creator<Address> CREATOR = new Creator<Address>() {
-        @Override
-        public Address createFromParcel(Parcel parcel) {
-            return new Address(parcel);
-        }
-
-        @Override
-        public Address[] newArray(int size) {
-            return new Address[size];
-        }
-    };
-
-    public Address(Parcel in) {
-        setName(in.readString());
-        setAddress(in.readString());
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeString(mName);
-        out.writeString(mAddress);
-    }
-}
diff --git a/src/com/android/mail/providers/Message.java b/src/com/android/mail/providers/Message.java
index 8bd8469..0611e91 100644
--- a/src/com/android/mail/providers/Message.java
+++ b/src/com/android/mail/providers/Message.java
@@ -31,6 +31,7 @@
 import android.text.util.Rfc822Token;
 import android.text.util.Rfc822Tokenizer;
 
+import com.android.emailcommon.mail.Address;
 import com.android.emailcommon.internet.MimeMessage;
 import com.android.emailcommon.internet.MimeUtility;
 import com.android.emailcommon.mail.MessagingException;
@@ -368,14 +369,14 @@
     public Message(Context context, MimeMessage mimeMessage, Uri emlFileUri)
             throws MessagingException {
         // Set message header values.
-        setFrom(com.android.emailcommon.mail.Address.pack(mimeMessage.getFrom()));
-        setTo(com.android.emailcommon.mail.Address.pack(mimeMessage.getRecipients(
+        setFrom(Address.pack(mimeMessage.getFrom()));
+        setTo(Address.pack(mimeMessage.getRecipients(
                 com.android.emailcommon.mail.Message.RecipientType.TO)));
-        setCc(com.android.emailcommon.mail.Address.pack(mimeMessage.getRecipients(
+        setCc(Address.pack(mimeMessage.getRecipients(
                 com.android.emailcommon.mail.Message.RecipientType.CC)));
-        setBcc(com.android.emailcommon.mail.Address.pack(mimeMessage.getRecipients(
+        setBcc(Address.pack(mimeMessage.getRecipients(
                 com.android.emailcommon.mail.Message.RecipientType.BCC)));
-        setReplyTo(com.android.emailcommon.mail.Address.pack(mimeMessage.getReplyTo()));
+        setReplyTo(Address.pack(mimeMessage.getReplyTo()));
         subject = mimeMessage.getSubject();
 
         final Date sentDate = mimeMessage.getSentDate();
diff --git a/src/com/android/mail/ui/AbstractConversationViewFragment.java b/src/com/android/mail/ui/AbstractConversationViewFragment.java
index 5425f7d..d70305b 100644
--- a/src/com/android/mail/ui/AbstractConversationViewFragment.java
+++ b/src/com/android/mail/ui/AbstractConversationViewFragment.java
@@ -30,7 +30,7 @@
 import android.view.MenuInflater;
 import android.view.MenuItem;
 
-import com.android.mail.ContactInfoSource;
+import com.android.emailcommon.mail.Address;
 import com.android.mail.R;
 import com.android.mail.analytics.Analytics;
 import com.android.mail.browse.ConversationAccountController;
@@ -43,7 +43,6 @@
 import com.android.mail.preferences.AccountPreferences;
 import com.android.mail.providers.Account;
 import com.android.mail.providers.AccountObserver;
-import com.android.mail.providers.Address;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
 import com.android.mail.providers.ListParams;
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index 76c4b4e..2b12a61 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -43,6 +43,7 @@
 import android.webkit.WebView;
 import android.widget.Button;
 
+import com.android.emailcommon.mail.Address;
 import com.android.mail.FormattedDateBuilder;
 import com.android.mail.R;
 import com.android.mail.analytics.Analytics;
@@ -68,7 +69,6 @@
 import com.android.mail.content.ObjectCursor;
 import com.android.mail.print.PrintUtils;
 import com.android.mail.providers.Account;
-import com.android.mail.providers.Address;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Message;
 import com.android.mail.providers.Settings;
@@ -1345,8 +1345,8 @@
             } else {
                 final Address addr = getAddress(senderAddress);
                 return res.getString(R.string.new_incoming_messages_one,
-                        sBidiFormatter.unicodeWrap(TextUtils.isEmpty(addr.getName())
-                        ? addr.getAddress() : addr.getName()));
+                        sBidiFormatter.unicodeWrap(TextUtils.isEmpty(addr.getPersonal())
+                        ? addr.getAddress() : addr.getPersonal()));
             }
         }
     }
diff --git a/src/com/android/mail/ui/NestedFolderTeaserView.java b/src/com/android/mail/ui/NestedFolderTeaserView.java
index c4d33e7..ebbdbdc 100644
--- a/src/com/android/mail/ui/NestedFolderTeaserView.java
+++ b/src/com/android/mail/ui/NestedFolderTeaserView.java
@@ -34,12 +34,12 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.emailcommon.mail.Address;
 import com.android.mail.R;
 import com.android.mail.browse.ConversationCursor;
 import com.android.mail.content.ObjectCursor;
 import com.android.mail.content.ObjectCursorLoader;
 import com.android.mail.providers.Account;
-import com.android.mail.providers.Address;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
 import com.android.mail.providers.MessageInfo;
@@ -52,14 +52,12 @@
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * The teaser list item in the conversation list that shows nested folders.
@@ -556,7 +554,7 @@
         final Address[] senderAddresses = Address.parse(unreadSenders);
 
         for (final Address senderAddress : senderAddresses) {
-            String sender = senderAddress.getName();
+            String sender = senderAddress.getPersonal();
             final String senderEmail = senderAddress.getAddress();
 
             if (!TextUtils.isEmpty(sender)) {
diff --git a/src/com/android/mail/ui/SecureConversationViewControllerCallbacks.java b/src/com/android/mail/ui/SecureConversationViewControllerCallbacks.java
index f53de7d..e4d32a0 100644
--- a/src/com/android/mail/ui/SecureConversationViewControllerCallbacks.java
+++ b/src/com/android/mail/ui/SecureConversationViewControllerCallbacks.java
@@ -21,11 +21,11 @@
 import android.net.Uri;
 import android.os.Handler;
 
+import com.android.emailcommon.mail.Address;
 import com.android.mail.ContactInfoSource;
 import com.android.mail.browse.ConversationAccountController;
 import com.android.mail.browse.ConversationViewHeader;
 import com.android.mail.browse.MessageHeaderView;
-import com.android.mail.providers.Address;
 
 import java.util.Map;
 
diff --git a/src/com/android/mail/ui/SecureConversationViewFragment.java b/src/com/android/mail/ui/SecureConversationViewFragment.java
index 051940c..8762b0c 100644
--- a/src/com/android/mail/ui/SecureConversationViewFragment.java
+++ b/src/com/android/mail/ui/SecureConversationViewFragment.java
@@ -26,6 +26,7 @@
 import android.view.ViewGroup;
 import android.webkit.WebView;
 
+import com.android.emailcommon.mail.Address;
 import com.android.mail.browse.ConversationAccountController;
 import com.android.mail.browse.ConversationMessage;
 import com.android.mail.browse.ConversationViewHeader;
@@ -33,7 +34,6 @@
 import com.android.mail.browse.MessageHeaderView;
 import com.android.mail.content.ObjectCursor;
 import com.android.mail.providers.Account;
-import com.android.mail.providers.Address;
 import com.android.mail.providers.Conversation;
 import com.android.mail.utils.LogTag;
 import com.android.mail.utils.LogUtils;
diff --git a/src/com/android/mail/utils/NotificationUtils.java b/src/com/android/mail/utils/NotificationUtils.java
index 694ac79..ef7fd70 100644
--- a/src/com/android/mail/utils/NotificationUtils.java
+++ b/src/com/android/mail/utils/NotificationUtils.java
@@ -41,11 +41,11 @@
 import android.util.Pair;
 import android.util.SparseArray;
 
+import com.android.emailcommon.mail.Address;
 import com.android.mail.EmailAddress;
 import com.android.mail.MailIntentService;
 import com.android.mail.R;
 import com.android.mail.analytics.Analytics;
-import com.android.mail.analytics.AnalyticsUtils;
 import com.android.mail.browse.MessageCursor;
 import com.android.mail.browse.SendersView;
 import com.android.mail.photomanager.LetterTileProvider;
@@ -53,7 +53,6 @@
 import com.android.mail.preferences.FolderPreferences;
 import com.android.mail.preferences.MailPrefs;
 import com.android.mail.providers.Account;
-import com.android.mail.providers.Address;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
 import com.android.mail.providers.Message;
@@ -1434,7 +1433,7 @@
         String displayableSender = address.getName();
 
         if (!TextUtils.isEmpty(displayableSender)) {
-            return Address.decodeAddressName(displayableSender);
+            return Address.decodeAddressPersonal(displayableSender);
         }
 
         // If that fails, default to the sender address.
diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java
index 459e090..9ca5510 100644
--- a/src/com/android/mail/utils/Utils.java
+++ b/src/com/android/mail/utils/Utils.java
@@ -16,12 +16,11 @@
 
 package com.android.mail.utils;
 
-import com.android.mail.providers.Address;
+import com.android.emailcommon.mail.Address;
 import com.google.android.mail.common.html.parser.HtmlDocument;
 import com.google.android.mail.common.html.parser.HtmlParser;
 import com.google.android.mail.common.html.parser.HtmlTree;
 import com.google.android.mail.common.html.parser.HtmlTreeBuilder;
-import com.google.common.collect.Maps;
 
 import android.app.ActivityManager;
 import android.app.Fragment;
@@ -44,7 +43,6 @@
 import android.text.SpannableString;
 import android.text.Spanned;
 import android.text.TextUtils;
-import android.text.TextUtils.SimpleStringSplitter;
 import android.text.style.CharacterStyle;
 import android.text.style.StyleSpan;
 import android.text.style.TextAppearanceSpan;
@@ -206,25 +204,6 @@
         return text.substring(0, realMax) + extension;
     }
 
-    /**
-     * Ensures that the given string starts and ends with the double quote
-     * character. The string is not modified in any way except to add the double
-     * quote character to start and end if it's not already there. sample ->
-     * "sample" "sample" -> "sample" ""sample"" -> "sample"
-     * "sample"" -> "sample" sa"mp"le -> "sa"mp"le" "sa"mp"le" -> "sa"mp"le"
-     * (empty string) -> "" " -> ""
-     */
-    public static String ensureQuotedString(String s) {
-        if (s == null) {
-            return null;
-        }
-        if (!s.matches("^\".*\"$")) {
-            return "\"" + s + "\"";
-        } else {
-            return s;
-        }
-    }
-
     private static int sMaxUnreadCount = -1;
     private static final CharacterStyle ACTION_BAR_UNREAD_STYLE = new StyleSpan(Typeface.BOLD);
     private static String sUnreadText;
diff --git a/tests/src/com/android/emailcommon/mail/AddressUnitTests.java b/tests/src/com/android/emailcommon/mail/AddressUnitTests.java
new file mode 100644
index 0000000..13bf686
--- /dev/null
+++ b/tests/src/com/android/emailcommon/mail/AddressUnitTests.java
@@ -0,0 +1,719 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.emailcommon.mail;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.apache.james.mime4j.decoder.DecoderUtil;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+/**
+ * This is a series of unit tests for the Address class.  These tests must be locally
+ * complete - no server(s) required.
+ */
+@SmallTest
+public class AddressUnitTests extends AndroidTestCase {
+
+    private static final String MULTI_ADDRESSES_LIST =
+            "noname1@dom1.com, "
+                    + "<noname2@dom2.com>, "
+                    + "simple name <address3@dom3.org>, "
+                    + "\"name,4\" <address4@dom4.org>,"
+                    + "\"big \\\"G\\\"\" <bigG@dom5.net>,"
+                    + "\u65E5\u672C\u8A9E <address6@co.jp>,"
+                    + "\"\u65E5\u672C\u8A9E\" <address7@co.jp>,"
+                    + "\uD834\uDF01\uD834\uDF46 <address8@ne.jp>,"
+                    + "\"\uD834\uDF01\uD834\uDF46\" <address9@ne.jp>,"
+                    + "noname@dom.com <noname@dom.com>" // personal == address
+            ;
+    private static final int MULTI_ADDRESSES_COUNT = 10;
+
+    private static final Address PACK_ADDR_1 = new Address("john@gmail.com", "John Doe");
+    private static final Address PACK_ADDR_2 = new Address("foo@bar.com", null);
+    private static final Address PACK_ADDR_3 = new Address(
+            "mar.y+test@gmail.com", "Mar-y, B; B*arr");
+    private static final Address[][] PACK_CASES = {
+            {PACK_ADDR_2}, {PACK_ADDR_1},
+            {PACK_ADDR_1, PACK_ADDR_2}, {PACK_ADDR_2, PACK_ADDR_1},
+            {PACK_ADDR_1, PACK_ADDR_3}, {PACK_ADDR_2, PACK_ADDR_2},
+            {PACK_ADDR_1, PACK_ADDR_2, PACK_ADDR_3}, {PACK_ADDR_3, PACK_ADDR_1, PACK_ADDR_2}
+    };
+
+    Address mAddress1;
+    Address mAddress2;
+    Address mAddress3;
+
+    /**
+     * Setup code.  We generate a handful of Address objects
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mAddress1 = new Address("address1", "personal1");
+        mAddress2 = new Address("address2", "");
+        mAddress3 = new Address("address3", null);
+    }
+
+    // see documentation of DecoderUtil.decodeEncodedWords() for details
+    private String padEncoded(String s) {
+        return "=?UTF-8?B?" + s + "?=";
+    }
+
+    /**
+     * Generate strings of incresing lenght by taking prefix substrings.
+     * For each of them, compare with the decoding of the precomputed base-64 encoding.
+     */
+    public void testBase64Decode() {
+        String testString = "xyza\0\"";
+        String base64Encoded[] = {"", "eA==", "eHk=", "eHl6", "eHl6YQ==", "eHl6YQA=", "eHl6YQAi"};
+        int len = testString.length();
+        for (int i = 1; i <= len; ++i) {
+            String encoded = padEncoded(base64Encoded[i]);
+            String decoded = DecoderUtil.decodeEncodedWords(encoded);
+            String prefix = testString.substring(0, i);
+            assertEquals(""+i, prefix, decoded);
+        }
+    }
+
+    /**
+     * Test for setAddress().
+     */
+    public void testSetAddress() {
+        String bareAddress = "user1@dom1.com";
+        String bracketAddress = "<user2@dom2.com>";
+
+        Address address = new Address(bareAddress);
+        assertEquals("bare address", "user1@dom1.com", address.getAddress());
+
+        address.setAddress(bracketAddress);
+        assertEquals("bracket address", "user2@dom2.com", address.getAddress());
+    }
+
+    /**
+     * Test for empty setPersonal().
+     */
+    public void brokentestNullPersonal() {
+        Address address = new Address("user1@dom1.org");
+        assertNull("no name", address.getPersonal());
+
+        address.setPersonal(null);
+        assertNull("null name", address.getPersonal());
+
+        address.setPersonal("");
+        assertNull("empty name", address.getPersonal());
+
+        address.setPersonal("\"\"");
+        assertNull("quoted empty address", address.getPersonal());
+    }
+
+    /**
+     * Test for setPersonal().
+     */
+    public void brokentestSetPersonal() {
+        Address address = new Address("user1@dom1.net", "simple name");
+        assertEquals("simple name", "simple name", address.getPersonal());
+
+        address.setPersonal("big \\\"G\\\"");
+        assertEquals("quoted name", "big \"G\"", address.getPersonal());
+
+        address.setPersonal("=?UTF-8?Q?big \"G\"?=");
+        assertEquals("quoted printable name", "big \"G\"", address.getPersonal());
+
+        address.setPersonal("=?UTF-8?B?YmlnICJHIg==?=");
+        assertEquals("base64 encoded name", "big \"G\"", address.getPersonal());
+    }
+
+    /**
+     * Test for setPersonal() with utf-16 and utf-32.
+     */
+    public void brokentestSetPersonalMultipleEncodings() {
+        Address address = new Address("user1@dom1.co.jp", "=?UTF-8?B?5bK45pys?=");
+        assertEquals("base64 utf-16 name", "\u5CB8\u672C", address.getPersonal());
+
+        address.setPersonal("\"=?UTF-8?Q?=E5=B2=B8=E6=9C=AC?=\"");
+        assertEquals("quoted printable utf-16 name", "\u5CB8\u672C", address.getPersonal());
+
+        address.setPersonal("=?ISO-2022-JP?B?GyRCNF9LXBsoQg==?=");
+        assertEquals("base64 jis encoded name", "\u5CB8\u672C", address.getPersonal());
+
+        address.setPersonal("\"=?UTF-8?B?8J2MgfCdjYY=?=\"");
+        assertEquals("base64 utf-32 name", "\uD834\uDF01\uD834\uDF46", address.getPersonal());
+
+        address.setPersonal("=?UTF-8?Q?=F0=9D=8C=81=F0=9D=8D=86?=");
+        assertEquals("quoted printable utf-32 name",
+                "\uD834\uDF01\uD834\uDF46", address.getPersonal());
+    }
+
+    /**
+     * TODO: more in-depth tests for parse()
+     */
+
+    /**
+     * Simple quick checks of empty-input edge conditions for parse()
+     *
+     * NOTE:  This is not a claim that these edge cases are "correct", only to maintain consistent
+     * behavior while I am changing some of the code in the function under test.
+     */
+    public void testEmptyParse() {
+        Address[] result;
+
+        // null input => empty array
+        result = Address.parse(null);
+        assertTrue("parsing null address", result != null && result.length == 0);
+
+        // empty string input => empty array
+        result = Address.parse("");
+        assertTrue("parsing zero-length", result != null && result.length == 0);
+
+        // spaces
+        result = Address.parse("   ");
+        assertTrue("parsing spaces", result != null && result.length == 0);
+
+        // spaces with comma
+        result = Address.parse("  ,  ");
+        assertTrue("parsing spaces with comma", result != null && result.length == 0);
+    }
+
+    /**
+     * Test parsing for single address.
+     */
+    public void testSingleParse() {
+        Address[] address1 = Address.parse("address1@dom1.com");
+        assertEquals("bare address count", 1, address1.length);
+        assertEquals("bare address", "address1@dom1.com", address1[0].getAddress());
+        assertNull("name of bare address", address1[0].getPersonal());
+
+        Address[] address2 = Address.parse("<address2@dom2.com>");
+        assertEquals("bracket address count", 1, address2.length);
+        assertEquals("bracket address", "address2@dom2.com", address2[0].getAddress());
+        assertNull("name of bracket address", address2[0].getPersonal());
+
+        Address[] address3 = Address.parse("first last <address3@dom3.org>");
+        assertEquals("address with name count", 1, address3.length);
+        assertEquals("address with name", "address3@dom3.org", address3[0].getAddress());
+        assertEquals("name of address with name", "first last", address3[0].getPersonal());
+
+        Address[] address4 = Address.parse("\"first,last\" <address4@dom4.org>");
+        assertEquals("address with quoted name count", 1, address4.length);
+        assertEquals("address with quoted name", "address4@dom4.org", address4[0].getAddress());
+        assertEquals("name of address with quoted name", "first,last", address4[0].getPersonal());
+    }
+
+    /**
+     * Test parsing for illegal address.
+     */
+    public void testIllegalParse() {
+        Address[] address1 = Address.parse("address1");
+        assertEquals("no atmark", 0, address1.length);
+
+        Address[] address2 = Address.parse("address2@");
+        assertEquals("no domain", 0, address2.length);
+
+        Address[] address3 = Address.parse("@dom3.com");
+        assertEquals("no local part", 0, address3.length);
+
+        Address[] address4 = Address.parse("address4@sub@dom4.org");
+        assertEquals("more than one atmark", 0, address4.length);
+
+        Address[] address5 = Address.parse("address5@dom5");
+        assertEquals("not dot in domain part", 0, address5.length);
+
+        Address[] address6 = Address.parse("address6@dom6.com.");
+        assertEquals("domain ends with dot", 0, address6.length);
+
+        Address[] address7 = Address.parse("address7@.dom7.org");
+        assertEquals("domain starts with dot", 0, address7.length);
+    }
+
+    /**
+     * Test parsing for address part.
+     */
+    public void testParsingAddress() {
+        Address[] addresses = Address.parse("address1@dom1.net, <address2@dom2.com>");
+        assertEquals("address count", 2, addresses.length);
+
+        assertEquals("bare address", "address1@dom1.net", addresses[0].getAddress());
+        assertNull("bare address name", addresses[0].getPersonal());
+
+        assertEquals("bracket address", "address2@dom2.com", addresses[1].getAddress());
+        assertNull("bracket address name", addresses[1].getPersonal());
+    }
+
+    /**
+     * Test parsing for simple name part.
+     */
+    public void testParsingSimpleName() {
+        Address[] addresses = Address.parse(
+                "name 1 <address1@dom1.net>, " +
+                        "\"name,2\" <address2@dom2.org>");
+        assertEquals("address count", 2, addresses.length);
+
+        assertEquals("bare name address", "address1@dom1.net", addresses[0].getAddress());
+        assertEquals("bare name", "name 1", addresses[0].getPersonal());
+
+        assertEquals("double quoted name address", "address2@dom2.org", addresses[1].getAddress());
+        assertEquals("double quoted name", "name,2", addresses[1].getPersonal());
+    }
+
+    /**
+     * Test parsing for utf-16 name part.
+     */
+    public void testParsingUtf16Name() {
+        Address[] addresses = Address.parse(
+                "\u3042\u3044\u3046 \u3048\u304A <address1@dom1.jp>, " +
+                        "\"\u3042\u3044\u3046,\u3048\u304A\" <address2@dom2.jp>");
+        assertEquals("address count", 2, addresses.length);
+
+        assertEquals("bare utf-16 name address", "address1@dom1.jp", addresses[0].getAddress());
+        assertEquals("bare utf-16 name",
+                "\u3042\u3044\u3046 \u3048\u304A", addresses[0].getPersonal());
+
+        assertEquals("double quoted utf-16 name address",
+                "address2@dom2.jp", addresses[1].getAddress());
+        assertEquals("double quoted utf-16 name",
+                "\u3042\u3044\u3046,\u3048\u304A", addresses[1].getPersonal());
+    }
+
+    /**
+     * Test parsing for utf-32 name part.
+     */
+    public void testParsingUtf32Name() {
+        Address[] addresses = Address.parse(
+                "\uD834\uDF01\uD834\uDF46 \uD834\uDF22 <address1@dom1.net>, " +
+                        "\"\uD834\uDF01\uD834\uDF46,\uD834\uDF22\" <address2@dom2.com>");
+        assertEquals("address count", 2, addresses.length);
+
+        assertEquals("bare utf-32 name address", "address1@dom1.net", addresses[0].getAddress());
+        assertEquals("bare utf-32 name",
+                "\uD834\uDF01\uD834\uDF46 \uD834\uDF22", addresses[0].getPersonal());
+
+        assertEquals("double quoted utf-32 name address",
+                "address2@dom2.com", addresses[1].getAddress());
+        assertEquals("double quoted utf-32 name",
+                "\uD834\uDF01\uD834\uDF46,\uD834\uDF22", addresses[1].getPersonal());
+    }
+
+    /**
+     * Test parsing for multi addresses.
+     */
+    public void testParseMulti() {
+        Address[] addresses = Address.parse(MULTI_ADDRESSES_LIST);
+
+        assertEquals("multi addrsses count", MULTI_ADDRESSES_COUNT, addresses.length);
+
+        assertEquals("no name 1 address", "noname1@dom1.com", addresses[0].getAddress());
+        assertNull("no name 1 name", addresses[0].getPersonal());
+        assertEquals("no name 2 address", "noname2@dom2.com", addresses[1].getAddress());
+        assertNull("no name 2 name", addresses[1].getPersonal());
+        assertEquals("simple name address", "address3@dom3.org", addresses[2].getAddress());
+        assertEquals("simple name name", "simple name", addresses[2].getPersonal());
+        assertEquals("double quoted name address", "address4@dom4.org", addresses[3].getAddress());
+        assertEquals("double quoted name name", "name,4", addresses[3].getPersonal());
+        assertEquals("quoted name address", "bigG@dom5.net", addresses[4].getAddress());
+        assertEquals("quoted name name", "big \"G\"", addresses[4].getPersonal());
+        assertEquals("utf-16 name address", "address6@co.jp", addresses[5].getAddress());
+        assertEquals("utf-16 name name", "\u65E5\u672C\u8A9E", addresses[5].getPersonal());
+        assertEquals("utf-16 quoted name address", "address7@co.jp", addresses[6].getAddress());
+        assertEquals("utf-16 quoted name name", "\u65E5\u672C\u8A9E",
+                addresses[6].getPersonal());
+        assertEquals("utf-32 name address", "address8@ne.jp", addresses[7].getAddress());
+        assertEquals("utf-32 name name", "\uD834\uDF01\uD834\uDF46", addresses[7].getPersonal());
+        assertEquals("utf-32 quoted name address", "address9@ne.jp", addresses[8].getAddress());
+        assertEquals("utf-32 quoted name name", "\uD834\uDF01\uD834\uDF46",
+                addresses[8].getPersonal());
+    }
+
+    /**
+     * Test various combinations of the toString (single) method
+     */
+    public void testToStringSingle() {
+        Address[] addresses = Address.parse(MULTI_ADDRESSES_LIST);
+
+        assertEquals("multi addrsses count", MULTI_ADDRESSES_COUNT, addresses.length);
+
+        // test for toString() results.
+        assertEquals("no name 1", "noname1@dom1.com", addresses[0].toString());
+        assertEquals("no name 2", "noname2@dom2.com", addresses[1].toString());
+        assertEquals("simple name", "simple name <address3@dom3.org>", addresses[2].toString());
+        assertEquals("double quoted name",
+                "\"name,4\" <address4@dom4.org>", addresses[3].toString());
+        assertEquals("quoted name", "\"big \"G\"\" <bigG@dom5.net>", addresses[4].toString());
+        assertEquals("utf-16 name", "\u65E5\u672C\u8A9E <address6@co.jp>",
+                addresses[5].toString());
+        assertEquals("utf-16 quoted name", "\u65E5\u672C\u8A9E <address7@co.jp>",
+                addresses[6].toString());
+        assertEquals("utf-32 name", "\uD834\uDF01\uD834\uDF46 <address8@ne.jp>",
+                addresses[7].toString());
+        assertEquals("utf-32 quoted name", "\uD834\uDF01\uD834\uDF46 <address9@ne.jp>",
+                addresses[8].toString());
+        assertEquals("name==address", "noname@dom.com", addresses[9].toString());
+    }
+
+    /**
+     * Test various combinations of the toString (multi) method
+     */
+    public void testToStringMulti() {
+        final Address[] address = Address.parse("noname1@dom1.com");
+        final Address[] addresses = Address.parse(MULTI_ADDRESSES_LIST);
+
+        assertEquals("multi addrsses count", MULTI_ADDRESSES_COUNT, addresses.length);
+
+        {
+            String line = Address.toString(address);
+            assertEquals("toString multi-1",
+                    "noname1@dom1.com",
+                    line);
+        }
+        {
+            String line = Address.toString(addresses);
+            assertEquals("toString multi-n",
+                    "noname1@dom1.com,"
+                            + "noname2@dom2.com,"
+                            + "simple name <address3@dom3.org>,"
+                            + "\"name,4\" <address4@dom4.org>,"
+                            + "\"big \"G\"\" <bigG@dom5.net>,"
+                            + "\u65E5\u672C\u8A9E <address6@co.jp>,"
+                            + "\u65E5\u672C\u8A9E <address7@co.jp>,"
+                            + "\uD834\uDF01\uD834\uDF46 <address8@ne.jp>,"
+                            + "\uD834\uDF01\uD834\uDF46 <address9@ne.jp>,"
+                            + "noname@dom.com",
+                    line);
+        }
+
+        // With custom separator
+        {
+            String line = Address.toString(address, "$");
+            assertEquals("toString multi-1",
+                    "noname1@dom1.com",
+                    line);
+        }
+
+        {
+            String line = Address.toString(addresses, "$");
+            assertEquals("toString multi-n",
+                    "noname1@dom1.com$"
+                            + "noname2@dom2.com$"
+                            + "simple name <address3@dom3.org>$"
+                            + "\"name,4\" <address4@dom4.org>$"
+                            + "\"big \"G\"\" <bigG@dom5.net>$"
+                            + "\u65E5\u672C\u8A9E <address6@co.jp>$"
+                            + "\u65E5\u672C\u8A9E <address7@co.jp>$"
+                            + "\uD834\uDF01\uD834\uDF46 <address8@ne.jp>$"
+                            + "\uD834\uDF01\uD834\uDF46 <address9@ne.jp>$"
+                            + "noname@dom.com",
+                    line);
+        }
+    }
+
+    /**
+     * Test parsing for quoted and encoded name part.
+     */
+    public void testParsingQuotedEncodedName() {
+        Address[] addresses = Address.parse(
+                "\"big \\\"G\\\"\" <bigG@dom1.com>, =?UTF-8?B?5pel5pys6Kqe?= <address2@co.jp>");
+
+        assertEquals("address count", 2, addresses.length);
+
+        assertEquals("quoted name address", "bigG@dom1.com", addresses[0].getAddress());
+        assertEquals("quoted name", "big \"G\"", addresses[0].getPersonal());
+
+        assertEquals("encoded name address", "address2@co.jp", addresses[1].getAddress());
+        assertEquals("encoded name", "\u65E5\u672C\u8A9E", addresses[1].getPersonal());
+    }
+
+    /**
+     * Test various combinations of the toHeader (single) method
+     */
+    public void testToHeaderSingle() {
+        Address noName1 = new Address("noname1@dom1.com");
+        Address noName2 = new Address("<noname2@dom2.com>", "");
+        Address simpleName = new Address("address3@dom3.org", "simple name");
+        Address dquoteName = new Address("address4@dom4.org", "name,4");
+        Address quotedName = new Address("bigG@dom5.net", "big \"G\"");
+        Address utf16Name = new Address("<address6@co.jp>", "\"\u65E5\u672C\u8A9E\"");
+        Address utf32Name = new Address("<address8@ne.jp>", "\uD834\uDF01\uD834\uDF46");
+        Address sameName = new Address("address@dom.org", "address@dom.org");
+
+        // test for internal states.
+        assertEquals("no name 1 address", "noname1@dom1.com", noName1.getAddress());
+        assertNull("no name 1 name", noName1.getPersonal());
+        assertEquals("no name 2 address", "noname2@dom2.com", noName2.getAddress());
+        assertNull("no name 2 name", noName2.getPersonal());
+        assertEquals("simple name address", "address3@dom3.org", simpleName.getAddress());
+        assertEquals("simple name name", "simple name", simpleName.getPersonal());
+        assertEquals("double quoted name address", "address4@dom4.org", dquoteName.getAddress());
+        assertEquals("double quoted name name", "name,4", dquoteName.getPersonal());
+        assertEquals("quoted name address", "bigG@dom5.net", quotedName.getAddress());
+        assertEquals("quoted name name", "big \"G\"", quotedName.getPersonal());
+        assertEquals("utf-16 name address", "address6@co.jp", utf16Name.getAddress());
+        assertEquals("utf-16 name name", "\u65E5\u672C\u8A9E", utf16Name.getPersonal());
+        assertEquals("utf-32 name address", "address8@ne.jp", utf32Name.getAddress());
+        assertEquals("utf-32 name name", "\uD834\uDF01\uD834\uDF46", utf32Name.getPersonal());
+        assertEquals("name == address address", "address@dom.org", sameName.getAddress());
+        assertEquals("name == address name", "address@dom.org", sameName.getPersonal());
+
+        // Test for toHeader() results.
+        assertEquals("no name 1", "noname1@dom1.com", noName1.toHeader());
+        assertEquals("no name 2", "noname2@dom2.com", noName2.toHeader());
+        assertEquals("simple name", "simple name <address3@dom3.org>", simpleName.toHeader());
+        assertEquals("double quoted name", "\"name,4\" <address4@dom4.org>", dquoteName.toHeader());
+        assertEquals("quoted name", "\"big \\\"G\\\"\" <bigG@dom5.net>", quotedName.toHeader());
+        assertEquals("utf-16 name", "=?UTF-8?B?5pel5pys6Kqe?= <address6@co.jp>",
+                utf16Name.toHeader());
+        assertEquals("utf-32 name", "=?UTF-8?B?8J2MgfCdjYY=?= <address8@ne.jp>",
+                utf32Name.toHeader());
+        assertEquals("name == address", "\"address@dom.org\" <address@dom.org>",
+                sameName.toHeader());
+    }
+
+    /**
+     * Test various combinations of the toHeader (multi) method
+     */
+    public void testToHeaderMulti() {
+        Address noName1 = new Address("noname1@dom1.com");
+        Address noName2 = new Address("<noname2@dom2.com>", "");
+        Address simpleName = new Address("address3@dom3.org", "simple name");
+        Address dquoteName = new Address("address4@dom4.org", "name,4");
+        Address quotedName = new Address("bigG@dom5.net", "big \"G\"");
+        Address utf16Name = new Address("<address6@co.jp>", "\"\u65E5\u672C\u8A9E\"");
+        Address utf32Name = new Address("<address8@ne.jp>", "\uD834\uDF01\uD834\uDF46");
+
+        // test for internal states.
+        assertEquals("no name 1 address", "noname1@dom1.com", noName1.getAddress());
+        assertNull("no name 1 name", noName1.getPersonal());
+        assertEquals("no name 2 address", "noname2@dom2.com", noName2.getAddress());
+        assertNull("no name 2 name", noName2.getPersonal());
+        assertEquals("simple name address", "address3@dom3.org", simpleName.getAddress());
+        assertEquals("simple name name", "simple name", simpleName.getPersonal());
+        assertEquals("double quoted name address", "address4@dom4.org", dquoteName.getAddress());
+        assertEquals("double quoted name name", "name,4", dquoteName.getPersonal());
+        assertEquals("quoted name address", "bigG@dom5.net", quotedName.getAddress());
+        assertEquals("quoted name name", "big \"G\"", quotedName.getPersonal());
+        assertEquals("utf-16 name address", "address6@co.jp", utf16Name.getAddress());
+        assertEquals("utf-16 name name", "\u65E5\u672C\u8A9E", utf16Name.getPersonal());
+        assertEquals("utf-32 name address", "address8@ne.jp", utf32Name.getAddress());
+        assertEquals("utf-32 name name", "\uD834\uDF01\uD834\uDF46", utf32Name.getPersonal());
+
+        Address[] addresses = new Address[] {
+                noName1, noName2, simpleName, dquoteName, quotedName, utf16Name, utf32Name,
+        };
+        String line = Address.toHeader(addresses);
+
+        assertEquals("toHeader() multi",
+                "noname1@dom1.com, "
+                        + "noname2@dom2.com, "
+                        + "simple name <address3@dom3.org>, "
+                        + "\"name,4\" <address4@dom4.org>, "
+                        + "\"big \\\"G\\\"\" <bigG@dom5.net>, "
+                        + "=?UTF-8?B?5pel5pys6Kqe?= <address6@co.jp>, "
+                        + "=?UTF-8?B?8J2MgfCdjYY=?= <address8@ne.jp>",
+                line);
+    }
+
+    /**
+     * Test various combinations of the toFriendly (single) method
+     */
+    public void testToFriendlySingle() {
+        assertEquals("personal1", mAddress1.toFriendly());
+        assertEquals("address2", mAddress2.toFriendly());
+        assertEquals("address3", mAddress3.toFriendly());
+    }
+
+    /**
+     * Test various combinations of the toFriendly (array) method
+     */
+    public void testToFriendlyArray() {
+        Address[] list1 = null;
+        Address[] list2 = new Address[0];
+        Address[] list3 = new Address[] { mAddress1 };
+        Address[] list4 = new Address[] { mAddress1, mAddress2, mAddress3 };
+
+        assertEquals(null, Address.toFriendly(list1));
+        assertEquals(null, Address.toFriendly(list2));
+        assertEquals("personal1", Address.toFriendly(list3));
+        assertEquals("personal1, address2, address3", Address.toFriendly(list4));
+    }
+
+    /**
+     * Simple quick checks of empty-input edge conditions for pack()
+     *
+     * NOTE:  This is not a claim that these edge cases are "correct", only to maintain consistent
+     * behavior while I am changing some of the code in the function under test.
+     */
+    public void testEmptyPack() {
+        String result;
+
+        // null input => null string
+        result = Address.pack(null);
+        assertNull("packing null", result);
+
+        // zero-length input => null string
+        result = Address.pack(new Address[] { });
+        assertNull("packing empty array", result);
+    }
+
+    /**
+     * Simple quick checks of empty-input edge conditions for unpack()
+     *
+     * NOTE:  This is not a claim that these edge cases are "correct", only to maintain consistent
+     * behavior while I am changing some of the code in the function under test.
+     */
+    public void testEmptyUnpack() {
+        Address[] result;
+
+        /*
+        // null input => empty array
+        result = Address.unpack(null);
+        assertTrue("unpacking null address", result != null && result.length == 0);
+        */
+        // empty string input => empty array
+        result = Address.unpack("");
+        assertTrue("unpacking zero-length", result != null && result.length == 0);
+    }
+
+    private static boolean addressEquals(Address a1, Address a2) {
+        if (!a1.equals(a2)) {
+            return false;
+        }
+        final String displayName1 = a1.getPersonal();
+        final String displayName2 = a2.getPersonal();
+        if (displayName1 == null) {
+            return displayName2 == null;
+        } else {
+            return displayName1.equals(displayName2);
+        }
+    }
+
+    private static boolean addressArrayEquals(Address[] array1, Address[] array2) {
+        if (array1.length != array2.length) {
+            return false;
+        }
+        for (int i = array1.length - 1; i >= 0; --i) {
+            if (!addressEquals(array1[i], array2[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void testPackUnpack() {
+        for (Address[] list : PACK_CASES) {
+            String packed = Address.pack(list);
+            assertTrue(packed, addressArrayEquals(list, Address.unpack(packed)));
+        }
+    }
+
+    /**
+     * Tests that unpackToString() returns the same result as toString(unpack()).
+     */
+    public void testUnpackToString() {
+        assertNull(Address.unpackToString(null));
+        assertNull(Address.unpackToString(""));
+
+        for (Address[] list : PACK_CASES) {
+            String packed = Address.pack(list);
+            String s1 = Address.unpackToString(packed);
+            String s2 = Address.toString(Address.unpack(packed));
+            assertEquals(s2, s2, s1);
+        }
+    }
+
+    /**
+     * Tests that parseAndPack() returns the same result as pack(parse()).
+     */
+    public void testParseAndPack() {
+        String s1 = Address.parseAndPack(MULTI_ADDRESSES_LIST);
+        String s2 = Address.pack(Address.parse(MULTI_ADDRESSES_LIST));
+        assertEquals(s2, s1);
+    }
+
+    public void testSinglePack() {
+        Address[] addrArray = new Address[1];
+        for (Address address : new Address[]{PACK_ADDR_1, PACK_ADDR_2, PACK_ADDR_3}) {
+            String packed1 = address.pack();
+            addrArray[0] = address;
+            String packed2 = Address.pack(addrArray);
+            assertEquals(packed1, packed2);
+        }
+    }
+
+    /**
+     * Tests that:
+     * 1. unpackFirst() with empty list returns null.
+     * 2. unpackFirst() with non-empty returns the same as unpack()[0]
+     */
+    public void testUnpackFirst() {
+        assertNull(Address.unpackFirst(null));
+        assertNull(Address.unpackFirst(""));
+
+        for (Address[] list : PACK_CASES) {
+            String packed = Address.pack(list);
+            Address[] array = Address.unpack(packed);
+            Address first = Address.unpackFirst(packed);
+            assertTrue(packed, addressEquals(array[0], first));
+        }
+    }
+
+    public void testIsValidAddress() {
+        String notValid[] = {"", "foo", "john@", "x@y", "x@y.", "foo.com"};
+        String valid[] = {"x@y.z", "john@gmail.com", "a@b.c.d"};
+        for (String address : notValid) {
+            assertTrue(address, !Address.isValidAddress(address));
+        }
+        for (String address : valid) {
+            assertTrue(address, Address.isValidAddress(address));
+        }
+
+        // isAllValid() must accept empty address list as valid
+        assertTrue("Empty address list is valid", Address.isAllValid(""));
+    }
+
+    /**
+     * Legacy pack() used for testing legacyUnpack().
+     * The packed list is a comma separated list of:
+     * URLENCODE(address)[;URLENCODE(personal)]
+     * @See pack()
+     */
+    private static String legacyPack(Address[] addresses) {
+        if (addresses == null) {
+            return null;
+        } else if (addresses.length == 0) {
+            return "";
+        }
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0, count = addresses.length; i < count; i++) {
+            Address address = addresses[i];
+            try {
+                sb.append(URLEncoder.encode(address.getAddress(), "UTF-8"));
+                if (address.getPersonal() != null) {
+                    sb.append(';');
+                    sb.append(URLEncoder.encode(address.getPersonal(), "UTF-8"));
+                }
+                if (i < count - 1) {
+                    sb.append(',');
+                }
+            }
+            catch (UnsupportedEncodingException uee) {
+                return null;
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/tests/src/com/android/mail/providers/AddressTests.java b/tests/src/com/android/mail/providers/AddressTests.java
deleted file mode 100644
index 01bd173..0000000
--- a/tests/src/com/android/mail/providers/AddressTests.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/**
- * Copyright (c) 2011, Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.mail.providers;
-
-import android.test.AndroidTestCase;
-
-public class AddressTests extends AndroidTestCase {
-
-    public void testIsValid() {
-        assertTrue(Address.isValidAddress("\"Daisuké Miyakawa (宮川 大輔)\" <dmiyakawa@google.com>"));
-        assertTrue(Address.isValidAddress("\"宮川 大輔\" <dmiyakawa@google.com>"));
-    }
-}
\ No newline at end of file