1345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein/* 2345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Copyright (C) 2008 The Android Open Source Project 3345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 4345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Licensed under the Apache License, Version 2.0 (the "License"); 5345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * you may not use this file except in compliance with the License. 6345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * You may obtain a copy of the License at 7345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 8345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * http://www.apache.org/licenses/LICENSE-2.0 9345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 10345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Unless required by applicable law or agreed to in writing, software 11345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * distributed under the License is distributed on an "AS IS" BASIS, 12345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * See the License for the specific language governing permissions and 14345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * limitations under the License. 15345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 16345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinpackage com.android.emailcommon.mail; 17345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 18821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport android.os.Parcel; 19821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport android.os.Parcelable; 20821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport android.text.Html; 21345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.text.TextUtils; 22345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.text.util.Rfc822Token; 23345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport android.text.util.Rfc822Tokenizer; 24345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 25821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport com.android.mail.utils.LogTag; 26821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerimport com.android.mail.utils.LogUtils; 27345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport com.google.common.annotations.VisibleForTesting; 28345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 29345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport org.apache.james.mime4j.codec.EncoderUtil; 30345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport org.apache.james.mime4j.decoder.DecoderUtil; 31345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 32345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.util.ArrayList; 33345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sappersteinimport java.util.regex.Pattern; 34345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 35345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein/** 36345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * This class represent email address. 37345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 38345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * RFC822 email address may have following format. 39345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * "name" <address> (comment) 40345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * "name" <address> 41345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * name <address> 42345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * address 43345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Name and comment part should be MIME/base64 encoded in header if necessary. 44345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 45345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 46821e578a71c7015646522e729600618f0ec16fc0Tony Mantlerpublic class Address implements Parcelable { 47821e578a71c7015646522e729600618f0ec16fc0Tony Mantler public static final String ADDRESS_DELIMETER = ","; 48345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 49345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Address part, in the form local_part@domain_part. No surrounding angle brackets. 50345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 51345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein private String mAddress; 52345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 53345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 54345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Name part. No surrounding double quote, and no MIME/base64 encoding. 55345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * This must be null if Address has no name part. 56345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 57345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein private String mPersonal; 58345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 59821e578a71c7015646522e729600618f0ec16fc0Tony Mantler /** 60821e578a71c7015646522e729600618f0ec16fc0Tony Mantler * When personal is set, it will return the first token of the personal 61821e578a71c7015646522e729600618f0ec16fc0Tony Mantler * string. Otherwise, it will return the e-mail address up to the '@' sign. 62821e578a71c7015646522e729600618f0ec16fc0Tony Mantler */ 63821e578a71c7015646522e729600618f0ec16fc0Tony Mantler private String mSimplifiedName; 64821e578a71c7015646522e729600618f0ec16fc0Tony Mantler 65345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // Regex that matches address surrounded by '<>' optionally. '^<?([^>]+)>?$' 66345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein private static final Pattern REMOVE_OPTIONAL_BRACKET = Pattern.compile("^<?([^>]+)>?$"); 67345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // Regex that matches personal name surrounded by '""' optionally. '^"?([^"]+)"?$' 68345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein private static final Pattern REMOVE_OPTIONAL_DQUOTE = Pattern.compile("^\"?([^\"]*)\"?$"); 69345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // Regex that matches escaped character '\\([\\"])' 70345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein private static final Pattern UNQUOTE = Pattern.compile("\\\\([\\\\\"])"); 71345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 72f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu // TODO: LOCAL_PART and DOMAIN_PART_PART are too permissive and can be improved. 73f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu // TODO: Fix this to better constrain comments. 74f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu /** Regex for the local part of an email address. */ 75f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu private static final String LOCAL_PART = "[^@]+"; 76f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu /** Regex for each part of the domain part, i.e. the thing between the dots. */ 77f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu private static final String DOMAIN_PART_PART = "[[\\w][\\d]\\-\\(\\)\\[\\]]+"; 78f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu /** Regex for the domain part, which is two or more {@link #DOMAIN_PART_PART} separated by . */ 79f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu private static final String DOMAIN_PART = 80f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu "(" + DOMAIN_PART_PART + "\\.)+" + DOMAIN_PART_PART; 81f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu 82f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu /** Pattern to check if an email address is valid. */ 83f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu private static final Pattern EMAIL_ADDRESS = 84f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu Pattern.compile("\\A" + LOCAL_PART + "@" + DOMAIN_PART + "\\z"); 85f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu 86345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein private static final Address[] EMPTY_ADDRESS_ARRAY = new Address[0]; 87345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 884d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux // delimiters are chars that do not appear in an email address, used by fromHeader 89345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein private static final char LIST_DELIMITER_EMAIL = '\1'; 90345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein private static final char LIST_DELIMITER_PERSONAL = '\2'; 91345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 92821e578a71c7015646522e729600618f0ec16fc0Tony Mantler private static final String LOG_TAG = LogTag.getLogTag(); 93821e578a71c7015646522e729600618f0ec16fc0Tony Mantler 94ede08646dbe563c90358ffec2c7397616ca1e523Scott Kennedy @VisibleForTesting 95821e578a71c7015646522e729600618f0ec16fc0Tony Mantler public Address(String address) { 96345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein setAddress(address); 97345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 98345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 99821e578a71c7015646522e729600618f0ec16fc0Tony Mantler public Address(String address, String personal) { 100821e578a71c7015646522e729600618f0ec16fc0Tony Mantler setPersonal(personal); 101345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein setAddress(address); 102345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 103345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 104821e578a71c7015646522e729600618f0ec16fc0Tony Mantler /** 105821e578a71c7015646522e729600618f0ec16fc0Tony Mantler * Returns a simplified string for this e-mail address. 106821e578a71c7015646522e729600618f0ec16fc0Tony Mantler * When a name is known, it will return the first token of that name. Otherwise, it will 107821e578a71c7015646522e729600618f0ec16fc0Tony Mantler * return the e-mail address up to the '@' sign. 108821e578a71c7015646522e729600618f0ec16fc0Tony Mantler */ 109821e578a71c7015646522e729600618f0ec16fc0Tony Mantler public String getSimplifiedName() { 110821e578a71c7015646522e729600618f0ec16fc0Tony Mantler if (mSimplifiedName == null) { 111821e578a71c7015646522e729600618f0ec16fc0Tony Mantler if (TextUtils.isEmpty(mPersonal) && !TextUtils.isEmpty(mAddress)) { 112821e578a71c7015646522e729600618f0ec16fc0Tony Mantler int atSign = mAddress.indexOf('@'); 113821e578a71c7015646522e729600618f0ec16fc0Tony Mantler mSimplifiedName = (atSign != -1) ? mAddress.substring(0, atSign) : ""; 114821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } else if (!TextUtils.isEmpty(mPersonal)) { 115821e578a71c7015646522e729600618f0ec16fc0Tony Mantler 116821e578a71c7015646522e729600618f0ec16fc0Tony Mantler // TODO: use Contacts' NameSplitter for more reliable first-name extraction 117821e578a71c7015646522e729600618f0ec16fc0Tony Mantler 118821e578a71c7015646522e729600618f0ec16fc0Tony Mantler int end = mPersonal.indexOf(' '); 119821e578a71c7015646522e729600618f0ec16fc0Tony Mantler while (end > 0 && mPersonal.charAt(end - 1) == ',') { 120821e578a71c7015646522e729600618f0ec16fc0Tony Mantler end--; 121821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } 122821e578a71c7015646522e729600618f0ec16fc0Tony Mantler mSimplifiedName = (end < 1) ? mPersonal : mPersonal.substring(0, end); 123821e578a71c7015646522e729600618f0ec16fc0Tony Mantler 124821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } else { 125821e578a71c7015646522e729600618f0ec16fc0Tony Mantler LogUtils.w(LOG_TAG, "Unable to get a simplified name"); 126821e578a71c7015646522e729600618f0ec16fc0Tony Mantler mSimplifiedName = ""; 127821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } 128821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } 129821e578a71c7015646522e729600618f0ec16fc0Tony Mantler return mSimplifiedName; 130821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } 131821e578a71c7015646522e729600618f0ec16fc0Tony Mantler 132821e578a71c7015646522e729600618f0ec16fc0Tony Mantler public static synchronized Address getEmailAddress(String rawAddress) { 133821e578a71c7015646522e729600618f0ec16fc0Tony Mantler if (TextUtils.isEmpty(rawAddress)) { 134821e578a71c7015646522e729600618f0ec16fc0Tony Mantler return null; 135821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } 136821e578a71c7015646522e729600618f0ec16fc0Tony Mantler String name, address; 137821e578a71c7015646522e729600618f0ec16fc0Tony Mantler final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(rawAddress); 138821e578a71c7015646522e729600618f0ec16fc0Tony Mantler if (tokens.length > 0) { 139821e578a71c7015646522e729600618f0ec16fc0Tony Mantler final String tokenizedName = tokens[0].getName(); 140821e578a71c7015646522e729600618f0ec16fc0Tony Mantler name = tokenizedName != null ? Html.fromHtml(tokenizedName.trim()).toString() 141821e578a71c7015646522e729600618f0ec16fc0Tony Mantler : ""; 142821e578a71c7015646522e729600618f0ec16fc0Tony Mantler address = Html.fromHtml(tokens[0].getAddress()).toString(); 143821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } else { 144821e578a71c7015646522e729600618f0ec16fc0Tony Mantler name = ""; 145821e578a71c7015646522e729600618f0ec16fc0Tony Mantler address = rawAddress == null ? 146821e578a71c7015646522e729600618f0ec16fc0Tony Mantler "" : Html.fromHtml(rawAddress).toString(); 147821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } 148821e578a71c7015646522e729600618f0ec16fc0Tony Mantler return new Address(address, name); 149821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } 150821e578a71c7015646522e729600618f0ec16fc0Tony Mantler 151345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public String getAddress() { 152345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return mAddress; 153345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 154345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 155345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public void setAddress(String address) { 156345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein mAddress = REMOVE_OPTIONAL_BRACKET.matcher(address).replaceAll("$1"); 157345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 158345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 159345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 160345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Get name part as UTF-16 string. No surrounding double quote, and no MIME/base64 encoding. 161345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 162345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return Name part of email address. Returns null if it is omitted. 163345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 164345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public String getPersonal() { 165345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return mPersonal; 166345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 167345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 168345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 169821e578a71c7015646522e729600618f0ec16fc0Tony Mantler * Set personal part from UTF-16 string. Optional surrounding double quote will be removed. 170345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * It will be also unquoted and MIME/base64 decoded. 171345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 172345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param personal name part of email address as UTF-16 string. Null is acceptable. 173345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 174345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public void setPersonal(String personal) { 175821e578a71c7015646522e729600618f0ec16fc0Tony Mantler mPersonal = decodeAddressPersonal(personal); 176821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } 177821e578a71c7015646522e729600618f0ec16fc0Tony Mantler 178821e578a71c7015646522e729600618f0ec16fc0Tony Mantler /** 179821e578a71c7015646522e729600618f0ec16fc0Tony Mantler * Decodes name from UTF-16 string. Optional surrounding double quote will be removed. 180821e578a71c7015646522e729600618f0ec16fc0Tony Mantler * It will be also unquoted and MIME/base64 decoded. 181821e578a71c7015646522e729600618f0ec16fc0Tony Mantler * 182821e578a71c7015646522e729600618f0ec16fc0Tony Mantler * @param personal name part of email address as UTF-16 string. Null is acceptable. 183821e578a71c7015646522e729600618f0ec16fc0Tony Mantler */ 184821e578a71c7015646522e729600618f0ec16fc0Tony Mantler public static String decodeAddressPersonal(String personal) { 185345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (personal != null) { 186345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein personal = REMOVE_OPTIONAL_DQUOTE.matcher(personal).replaceAll("$1"); 187345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein personal = UNQUOTE.matcher(personal).replaceAll("$1"); 188345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein personal = DecoderUtil.decodeEncodedWords(personal); 189345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (personal.length() == 0) { 190345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein personal = null; 191345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 192345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 193821e578a71c7015646522e729600618f0ec16fc0Tony Mantler return personal; 194345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 195345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 196345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 197345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * This method is used to check that all the addresses that the user 198345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * entered in a list (e.g. To:) are valid, so that none is dropped. 199345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 20023c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy @VisibleForTesting 201345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static boolean isAllValid(String addressList) { 202345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // This code mimics the parse() method below. 203345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // I don't know how to better avoid the code-duplication. 204345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (addressList != null && addressList.length() > 0) { 205345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList); 206345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein for (int i = 0, length = tokens.length; i < length; ++i) { 207345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein Rfc822Token token = tokens[i]; 208345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String address = token.getAddress(); 209345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (!TextUtils.isEmpty(address) && !isValidAddress(address)) { 210345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return false; 211345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 212345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 213345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 214345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return true; 215345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 216345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 217345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 218345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Parse a comma-delimited list of addresses in RFC822 format and return an 219345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * array of Address objects. 220345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 221345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param addressList Address list in comma-delimited string. 222345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return An array of 0 or more Addresses. 223345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 224345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static Address[] parse(String addressList) { 225345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (addressList == null || addressList.length() == 0) { 226345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return EMPTY_ADDRESS_ARRAY; 227345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 228345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(addressList); 229345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein ArrayList<Address> addresses = new ArrayList<Address>(); 230345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein for (int i = 0, length = tokens.length; i < length; ++i) { 231345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein Rfc822Token token = tokens[i]; 232345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String address = token.getAddress(); 233345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (!TextUtils.isEmpty(address)) { 234345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (isValidAddress(address)) { 235345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein String name = token.getName(); 236345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (TextUtils.isEmpty(name)) { 237345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein name = null; 238345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 239345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein addresses.add(new Address(address, name)); 240345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 241345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 242345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 243821e578a71c7015646522e729600618f0ec16fc0Tony Mantler return addresses.toArray(new Address[addresses.size()]); 244345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 245345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 246345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 247345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Checks whether a string email address is valid. 248345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * E.g. name@domain.com is valid. 249345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 250345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein @VisibleForTesting 251f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu static boolean isValidAddress(final String address) { 252f24a125e5fedaae7bdc4eeceeb542cc5a7f3bdd9Yu Ping Hu return EMAIL_ADDRESS.matcher(address).find(); 253345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 254345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 255345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein @Override 256345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public boolean equals(Object o) { 257345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (o instanceof Address) { 258345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // It seems that the spec says that the "user" part is case-sensitive, 259345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // while the domain part in case-insesitive. 260345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // So foo@yahoo.com and Foo@yahoo.com are different. 261345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // This may seem non-intuitive from the user POV, so we 262345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // may re-consider it if it creates UI trouble. 263345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // A problem case is "replyAll" sending to both 264345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // a@b.c and to A@b.c, which turn out to be the same on the server. 265345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // Leave unchanged for now (i.e. case-sensitive). 266345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return getAddress().equals(((Address) o).getAddress()); 267345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 268345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return super.equals(o); 269345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 270345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 271aa27bc0e1c3bb6be4609b00007637a9d3e960f5eScott Kennedy @Override 272345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public int hashCode() { 273345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return getAddress().hashCode(); 274345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 275345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 276345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 277345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Get human readable address string. 278345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Do not use this for email header. 279345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 280345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return Human readable address string. Not quoted and not encoded. 281345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 282345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein @Override 283345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public String toString() { 284345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (mPersonal != null && !mPersonal.equals(mAddress)) { 285345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (mPersonal.matches(".*[\\(\\)<>@,;:\\\\\".\\[\\]].*")) { 286821e578a71c7015646522e729600618f0ec16fc0Tony Mantler return ensureQuotedString(mPersonal) + " <" + mAddress + ">"; 287345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } else { 288345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return mPersonal + " <" + mAddress + ">"; 289345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 290345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } else { 291345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return mAddress; 292345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 293345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 294345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 295345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 296345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Ensures that the given string starts and ends with the double quote character. The string is 297345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * not modified in any way except to add the double quote character to start and end if it's not 298345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * already there. 299345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 300345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * sample -> "sample" 301345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * "sample" -> "sample" 302345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * ""sample"" -> "sample" 303345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * "sample"" -> "sample" 304345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * sa"mp"le -> "sa"mp"le" 305345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * "sa"mp"le" -> "sa"mp"le" 306345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * (empty string) -> "" 307345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * " -> "" 308345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 309821e578a71c7015646522e729600618f0ec16fc0Tony Mantler private static String ensureQuotedString(String s) { 310345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (s == null) { 311345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return null; 312345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 313345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (!s.matches("^\".*\"$")) { 314345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return "\"" + s + "\""; 315821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } else { 316345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return s; 317345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 318345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 319345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 320345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 321345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Get human readable comma-delimited address string. 322345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 323345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param addresses Address array 324345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return Human readable comma-delimited address string. 325345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 32623c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy @VisibleForTesting 327345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static String toString(Address[] addresses) { 328821e578a71c7015646522e729600618f0ec16fc0Tony Mantler return toString(addresses, ADDRESS_DELIMETER); 329345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 330345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 331345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 332345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Get human readable address strings joined with the specified separator. 333345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 334345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param addresses Address array 335345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param separator Separator 336345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return Human readable comma-delimited address string. 337345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 338345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static String toString(Address[] addresses, String separator) { 339345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (addresses == null || addresses.length == 0) { 340345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return null; 341345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 342345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (addresses.length == 1) { 343345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return addresses[0].toString(); 344345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 345821e578a71c7015646522e729600618f0ec16fc0Tony Mantler StringBuilder sb = new StringBuilder(addresses[0].toString()); 346345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein for (int i = 1; i < addresses.length; i++) { 347345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein sb.append(separator); 348345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // TODO: investigate why this .trim() is needed. 349345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein sb.append(addresses[i].toString().trim()); 350345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 351345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return sb.toString(); 352345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 353345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 354345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 355345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Get RFC822/MIME compatible address string. 356345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 357345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return RFC822/MIME compatible address string. 358345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * It may be surrounded by double quote or quoted and MIME/base64 encoded if necessary. 359345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 360345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public String toHeader() { 361345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (mPersonal != null) { 362345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return EncoderUtil.encodeAddressDisplayName(mPersonal) + " <" + mAddress + ">"; 363345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } else { 364345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return mAddress; 365345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 366345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 367345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 368345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 369345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Get RFC822/MIME compatible comma-delimited address string. 370345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 371345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param addresses Address array 372345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return RFC822/MIME compatible comma-delimited address string. 373345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * it may be surrounded by double quoted or quoted and MIME/base64 encoded if necessary. 374345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 375345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static String toHeader(Address[] addresses) { 376345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (addresses == null || addresses.length == 0) { 377345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return null; 378345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 379345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (addresses.length == 1) { 380345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return addresses[0].toHeader(); 381345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 382821e578a71c7015646522e729600618f0ec16fc0Tony Mantler StringBuilder sb = new StringBuilder(addresses[0].toHeader()); 383345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein for (int i = 1; i < addresses.length; i++) { 384345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // We need space character to be able to fold line. 385345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein sb.append(", "); 386345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein sb.append(addresses[i].toHeader()); 387345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 388345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return sb.toString(); 389345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 390345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 391345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 392345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Get Human friendly address string. 393345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 394345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return the personal part of this Address, or the address part if the 395345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * personal part is not available 396345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 39723c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy @VisibleForTesting 398345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public String toFriendly() { 399345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (mPersonal != null && mPersonal.length() > 0) { 400345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return mPersonal; 401345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } else { 402345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return mAddress; 403345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 404345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 405345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 406345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 407345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * Creates a comma-delimited list of addresses in the "friendly" format (see toFriendly() for 408345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * details on the per-address conversion). 409345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * 410345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @param addresses Array of Address[] values 411345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * @return A comma-delimited string listing all of the addresses supplied. Null if source 412345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * was null or empty. 413345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 4141fa009551845a6454ca2b31cfd20999b113e7890Scott Kennedy @VisibleForTesting 415345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein public static String toFriendly(Address[] addresses) { 416345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (addresses == null || addresses.length == 0) { 417345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return null; 418345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 419345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (addresses.length == 1) { 420345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return addresses[0].toFriendly(); 421345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 422821e578a71c7015646522e729600618f0ec16fc0Tony Mantler StringBuilder sb = new StringBuilder(addresses[0].toFriendly()); 423345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein for (int i = 1; i < addresses.length; i++) { 424345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein sb.append(", "); 425345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein sb.append(addresses[i].toFriendly()); 426345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 427345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return sb.toString(); 428345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 429345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 430345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 4314d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux * Returns exactly the same result as Address.toString(Address.fromHeader(addressList)). 432345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 43323c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy @VisibleForTesting 4344d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux public static String fromHeaderToString(String addressList) { 4354d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux return toString(fromHeader(addressList)); 436345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 437345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 438345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 4394d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux * Returns exactly the same result as Address.toHeader(Address.parse(addressList)). 440345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 44123c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy @VisibleForTesting 4424d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux public static String parseToHeader(String addressList) { 4434d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux return Address.toHeader(Address.parse(addressList)); 444345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 445345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 446345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 4474d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux * Returns null if the addressList has 0 addresses, otherwise returns the first address. 4484d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux * The same as Address.fromHeader(addressList)[0] for non-empty list. 449345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * This is an utility method that offers some performance optimization opportunities. 450345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 45123c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy @VisibleForTesting 4524d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux public static Address firstAddress(String addressList) { 4534d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux Address[] array = fromHeader(addressList); 454345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return array.length > 0 ? array[0] : null; 455345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 456345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 457345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 4584d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux * This method exists to convert an address list formatted in a deprecated legacy format to the 4594d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux * standard RFC822 header format. {@link #fromHeader(String)} is capable of reading the legacy 4604d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux * format and the RFC822 format. {@link #toHeader()} always produces the RFC822 format. 4614d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux * 462345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * This implementation is brute-force, and could be replaced with a more efficient version 463345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein * if desired. 464345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 4654d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux public static String reformatToHeader(String addressList) { 4664d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux return toHeader(fromHeader(addressList)); 467345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 468345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 469345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /** 4704d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux * @param addressList a CSV of RFC822 addresses or the deprecated legacy string format 4714d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux * @return array of addresses parsed from <code>addressList</code> 472345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 47323c95321afcccdfebcaff33f8be07a07b8c435d7Scott Kennedy @VisibleForTesting 4744d3e937bb3ca208c320e8a124c6f26079c4090d0James Lemieux public static Address[] fromHeader(String addressList) { 475345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (addressList == null || addressList.length() == 0) { 476345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return EMPTY_ADDRESS_ARRAY; 477345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 478345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // IF we're CSV, just parse 479345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if ((addressList.indexOf(LIST_DELIMITER_PERSONAL) == -1) && 480345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein (addressList.indexOf(LIST_DELIMITER_EMAIL) == -1)) { 481345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein return Address.parse(addressList); 482345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 483821e578a71c7015646522e729600618f0ec16fc0Tony Mantler // Otherwise, do backward-compatible unpack 484345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein ArrayList<Address> addresses = new ArrayList<Address>(); 485345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein int length = addressList.length(); 486345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein int pairStartIndex = 0; 487821e578a71c7015646522e729600618f0ec16fc0Tony Mantler int pairEndIndex; 488345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 489345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein /* addressEndIndex is only re-scanned (indexOf()) when a LIST_DELIMITER_PERSONAL 490345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein is used, not for every email address; i.e. not for every iteration of the while(). 491345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein This reduces the theoretical complexity from quadratic to linear, 492345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein and provides some speed-up in practice by removing redundant scans of the string. 493345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein */ 494345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein int addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL); 495345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 496345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein while (pairStartIndex < length) { 497345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein pairEndIndex = addressList.indexOf(LIST_DELIMITER_EMAIL, pairStartIndex); 498345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (pairEndIndex == -1) { 499345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein pairEndIndex = length; 500345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 501345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein Address address; 502345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein if (addressEndIndex == -1 || pairEndIndex <= addressEndIndex) { 503345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // in this case the DELIMITER_PERSONAL is in a future pair, 504345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // so don't use personal, and don't update addressEndIndex 505345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein address = new Address(addressList.substring(pairStartIndex, pairEndIndex), null); 506345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } else { 507345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein address = new Address(addressList.substring(pairStartIndex, addressEndIndex), 508821e578a71c7015646522e729600618f0ec16fc0Tony Mantler addressList.substring(addressEndIndex + 1, pairEndIndex)); 509345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein // only update addressEndIndex when we use the LIST_DELIMITER_PERSONAL 510345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein addressEndIndex = addressList.indexOf(LIST_DELIMITER_PERSONAL, pairEndIndex + 1); 511345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 512345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein addresses.add(address); 513345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein pairStartIndex = pairEndIndex + 1; 514345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 515821e578a71c7015646522e729600618f0ec16fc0Tony Mantler return addresses.toArray(new Address[addresses.size()]); 516345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 517345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein 518821e578a71c7015646522e729600618f0ec16fc0Tony Mantler public static final Creator<Address> CREATOR = new Creator<Address>() { 519821e578a71c7015646522e729600618f0ec16fc0Tony Mantler @Override 520821e578a71c7015646522e729600618f0ec16fc0Tony Mantler public Address createFromParcel(Parcel parcel) { 521821e578a71c7015646522e729600618f0ec16fc0Tony Mantler return new Address(parcel); 522345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 523821e578a71c7015646522e729600618f0ec16fc0Tony Mantler 524821e578a71c7015646522e729600618f0ec16fc0Tony Mantler @Override 525821e578a71c7015646522e729600618f0ec16fc0Tony Mantler public Address[] newArray(int size) { 526821e578a71c7015646522e729600618f0ec16fc0Tony Mantler return new Address[size]; 527821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } 528821e578a71c7015646522e729600618f0ec16fc0Tony Mantler }; 529821e578a71c7015646522e729600618f0ec16fc0Tony Mantler 530821e578a71c7015646522e729600618f0ec16fc0Tony Mantler public Address(Parcel in) { 531821e578a71c7015646522e729600618f0ec16fc0Tony Mantler setPersonal(in.readString()); 532821e578a71c7015646522e729600618f0ec16fc0Tony Mantler setAddress(in.readString()); 533821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } 534821e578a71c7015646522e729600618f0ec16fc0Tony Mantler 535821e578a71c7015646522e729600618f0ec16fc0Tony Mantler @Override 536821e578a71c7015646522e729600618f0ec16fc0Tony Mantler public int describeContents() { 537821e578a71c7015646522e729600618f0ec16fc0Tony Mantler return 0; 538821e578a71c7015646522e729600618f0ec16fc0Tony Mantler } 539821e578a71c7015646522e729600618f0ec16fc0Tony Mantler 540821e578a71c7015646522e729600618f0ec16fc0Tony Mantler @Override 541821e578a71c7015646522e729600618f0ec16fc0Tony Mantler public void writeToParcel(Parcel out, int flags) { 542821e578a71c7015646522e729600618f0ec16fc0Tony Mantler out.writeString(mPersonal); 543821e578a71c7015646522e729600618f0ec16fc0Tony Mantler out.writeString(mAddress); 544345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein } 545345c43e12db42f6bdc0c15ac1c96af10164a458cAndrew Sapperstein} 546