am 85e9cc3b: (-s ours) Import translations. DO NOT MERGE

* commit '85e9cc3b7072025bbb305015120348d3b325210c':
  Import translations. DO NOT MERGE
diff --git a/chips/Android.mk b/chips/Android.mk
index 4a7977a..6aa8a01 100644
--- a/chips/Android.mk
+++ b/chips/Android.mk
@@ -21,4 +21,9 @@
      $(call all-java-files-under, src) \
      $(call all-logtags-files-under, src)
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+##################################################
+# Build all sub-directories
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/chips/res/drawable-hdpi/ic_contact_picture.png b/chips/res/drawable-hdpi/ic_contact_picture.png
index 2eef7b5..4c0e35e 100644
--- a/chips/res/drawable-hdpi/ic_contact_picture.png
+++ b/chips/res/drawable-hdpi/ic_contact_picture.png
Binary files differ
diff --git a/chips/res/drawable-mdpi/ic_contact_picture.png b/chips/res/drawable-mdpi/ic_contact_picture.png
index 6c7cb61..ead9718 100644
--- a/chips/res/drawable-mdpi/ic_contact_picture.png
+++ b/chips/res/drawable-mdpi/ic_contact_picture.png
Binary files differ
diff --git a/chips/res/drawable-xhdpi/ic_contact_picture.png b/chips/res/drawable-xhdpi/ic_contact_picture.png
index 1a2bfde..05a65f6 100644
--- a/chips/res/drawable-xhdpi/ic_contact_picture.png
+++ b/chips/res/drawable-xhdpi/ic_contact_picture.png
Binary files differ
diff --git a/chips/res/values-af/strings.xml b/chips/res/values-af/strings.xml
index 8b6ac01..3ccbcba 100644
--- a/chips/res/values-af/strings.xml
+++ b/chips/res/values-af/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> meer..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopieer e-posadres"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopieer foonnommer"</string>
     <string name="done" msgid="2356320650733788862">"Terugkeer"</string>
diff --git a/chips/res/values-am/strings.xml b/chips/res/values-am/strings.xml
index 52a7655..0537868 100644
--- a/chips/res/values-am/strings.xml
+++ b/chips/res/values-am/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> ተጨማሪ..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"የኢሜይል አድራሻ ቅዳ"</string>
     <string name="copy_number" msgid="530057841276106843">"የስልክ ቁጥር ቅዳ"</string>
     <string name="done" msgid="2356320650733788862">"መልስ"</string>
diff --git a/chips/res/values-ar/strings.xml b/chips/res/values-ar/strings.xml
index 2346b29..3398641 100644
--- a/chips/res/values-ar/strings.xml
+++ b/chips/res/values-ar/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> من المستلمين الآخرين..."</string>
+    <string name="more_string" msgid="8495478259330621990">"<xliff:g id="COUNT">%1$s</xliff:g>+"</string>
     <string name="copy_email" msgid="7869435992461603532">"نسخ عنوان البريد الإلكتروني"</string>
     <string name="copy_number" msgid="530057841276106843">"نسخ رقم الهاتف"</string>
     <string name="done" msgid="2356320650733788862">"رجوع"</string>
diff --git a/chips/res/values-be/strings.xml b/chips/res/values-be/strings.xml
index d8f5e7d..e71f0e2 100644
--- a/chips/res/values-be/strings.xml
+++ b/chips/res/values-be/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"Больш на <xliff:g id="COUNT">%1$s</xliff:g>..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Скапіраваць адрас электроннай пошты"</string>
     <string name="copy_number" msgid="530057841276106843">"Скапіраваць нумар тэлефона"</string>
     <string name="done" msgid="2356320650733788862">"Назад"</string>
diff --git a/chips/res/values-bg/strings.xml b/chips/res/values-bg/strings.xml
index 57993b2..06ed49e 100644
--- a/chips/res/values-bg/strings.xml
+++ b/chips/res/values-bg/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"Още <xliff:g id="COUNT">%1$s</xliff:g>..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+ <xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Копиране на имейл адреса"</string>
     <string name="copy_number" msgid="530057841276106843">"Копиране на телефонния номер"</string>
     <string name="done" msgid="2356320650733788862">"Enter"</string>
diff --git a/chips/res/values-ca/strings.xml b/chips/res/values-ca/strings.xml
index 7ba550b..77d2196 100644
--- a/chips/res/values-ca/strings.xml
+++ b/chips/res/values-ca/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> més..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Copia l\'adreça electrònica"</string>
     <string name="copy_number" msgid="530057841276106843">"Copia el número de telèfon"</string>
     <string name="done" msgid="2356320650733788862">"Retorn"</string>
diff --git a/chips/res/values-cs/strings.xml b/chips/res/values-cs/strings.xml
index ffd8b12..212d734 100644
--- a/chips/res/values-cs/strings.xml
+++ b/chips/res/values-cs/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"Ještě <xliff:g id="COUNT">%1$s</xliff:g>..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopírovat e-mailovou adresu"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopírovat telefonní číslo"</string>
     <string name="done" msgid="2356320650733788862">"Enter"</string>
diff --git a/chips/res/values-da/strings.xml b/chips/res/values-da/strings.xml
index 13cc591..8196fee 100644
--- a/chips/res/values-da/strings.xml
+++ b/chips/res/values-da/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> flere..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopiér e-mailadressen"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopiér telefonnummeret"</string>
     <string name="done" msgid="2356320650733788862">"Tilbage"</string>
diff --git a/chips/res/values-de/strings.xml b/chips/res/values-de/strings.xml
index 940ef94..d4c2fe4 100644
--- a/chips/res/values-de/strings.xml
+++ b/chips/res/values-de/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> weitere..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"E-Mail-Adresse kopieren"</string>
     <string name="copy_number" msgid="530057841276106843">"Telefonnummer kopieren"</string>
     <string name="done" msgid="2356320650733788862">"Eingabe"</string>
diff --git a/chips/res/values-el/strings.xml b/chips/res/values-el/strings.xml
index b1745f8..51b5ac3 100644
--- a/chips/res/values-el/strings.xml
+++ b/chips/res/values-el/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> περισσότεροι..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Αντιγραφή διεύθυνσης ηλεκτρονικού ταχυδρομείου"</string>
     <string name="copy_number" msgid="530057841276106843">"Αντιγραφή αριθμού τηλεφώνου"</string>
     <string name="done" msgid="2356320650733788862">"Πλήκτρο Return"</string>
diff --git a/chips/res/values-en-rGB/strings.xml b/chips/res/values-en-rGB/strings.xml
index d8d79dd..1ae784b 100644
--- a/chips/res/values-en-rGB/strings.xml
+++ b/chips/res/values-en-rGB/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> more..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Copy email address"</string>
     <string name="copy_number" msgid="530057841276106843">"Copy phone number"</string>
     <string name="done" msgid="2356320650733788862">"Return"</string>
diff --git a/chips/res/values-es-rUS/strings.xml b/chips/res/values-es-rUS/strings.xml
index ca09dbc..c63e6cb 100644
--- a/chips/res/values-es-rUS/strings.xml
+++ b/chips/res/values-es-rUS/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> más..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Copiar la dirección de correo"</string>
     <string name="copy_number" msgid="530057841276106843">"Copiar el número de teléfono"</string>
     <string name="done" msgid="2356320650733788862">"Volver"</string>
diff --git a/chips/res/values-es/strings.xml b/chips/res/values-es/strings.xml
index f20276d..74478a8 100644
--- a/chips/res/values-es/strings.xml
+++ b/chips/res/values-es/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> más..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Copiar dirección de correo electrónico"</string>
     <string name="copy_number" msgid="530057841276106843">"Copiar número de teléfono"</string>
     <string name="done" msgid="2356320650733788862">"Intro"</string>
diff --git a/chips/res/values-et/strings.xml b/chips/res/values-et/strings.xml
index 17a78d3..f32e66d 100644
--- a/chips/res/values-et/strings.xml
+++ b/chips/res/values-et/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"Veel <xliff:g id="COUNT">%1$s</xliff:g> ..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopeeri e-posti aadress"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopeeri telefoninumber"</string>
     <string name="done" msgid="2356320650733788862">"Sisestus"</string>
diff --git a/chips/res/values-fa/strings.xml b/chips/res/values-fa/strings.xml
index 47d8838..e5b32ba 100644
--- a/chips/res/values-fa/strings.xml
+++ b/chips/res/values-fa/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> مورد دیگر..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"کپی آدرس ایمیل"</string>
     <string name="copy_number" msgid="530057841276106843">"کپی شماره تلفن"</string>
     <string name="done" msgid="2356320650733788862">"بازگشت"</string>
diff --git a/chips/res/values-fi/strings.xml b/chips/res/values-fi/strings.xml
index d077975..9893923 100644
--- a/chips/res/values-fi/strings.xml
+++ b/chips/res/values-fi/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> lisää..."</string>
+    <string name="more_string" msgid="8495478259330621990">"yli <xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopioi sähköpostiosoite"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopioi puhelinnumero"</string>
     <string name="done" msgid="2356320650733788862">"Enter"</string>
diff --git a/chips/res/values-fr/strings.xml b/chips/res/values-fr/strings.xml
index e14945e..3d13b5f 100644
--- a/chips/res/values-fr/strings.xml
+++ b/chips/res/values-fr/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> autres..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+ <xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Copier l\'adresse e-mail"</string>
     <string name="copy_number" msgid="530057841276106843">"Copier le numéro de téléphone"</string>
     <string name="done" msgid="2356320650733788862">"Entrée"</string>
diff --git a/chips/res/values-hi/strings.xml b/chips/res/values-hi/strings.xml
index e65dd95..8368c0d 100644
--- a/chips/res/values-hi/strings.xml
+++ b/chips/res/values-hi/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> अधिक..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"ईमेल पते की प्रतिलिपि बनाएं"</string>
     <string name="copy_number" msgid="530057841276106843">"फोन नंबर की प्रतिलिपि बनाएं"</string>
     <string name="done" msgid="2356320650733788862">"वापस लौटें"</string>
diff --git a/chips/res/values-hr/strings.xml b/chips/res/values-hr/strings.xml
index 99213ed..65f2c12 100644
--- a/chips/res/values-hr/strings.xml
+++ b/chips/res/values-hr/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"Još <xliff:g id="COUNT">%1$s</xliff:g>..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopiranje e-adrese"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopiranje telefonskog broja"</string>
     <string name="done" msgid="2356320650733788862">"Vrati"</string>
diff --git a/chips/res/values-hu/strings.xml b/chips/res/values-hu/strings.xml
index 5fbabb4..a18f811 100644
--- a/chips/res/values-hu/strings.xml
+++ b/chips/res/values-hu/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> további..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"E-mail cím másolása"</string>
     <string name="copy_number" msgid="530057841276106843">"Telefonszám másolása"</string>
     <string name="done" msgid="2356320650733788862">"Enter"</string>
diff --git a/chips/res/values-in/strings.xml b/chips/res/values-in/strings.xml
index 38ee7ea..9c1cbe6 100644
--- a/chips/res/values-in/strings.xml
+++ b/chips/res/values-in/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> lainnya..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Salin alamat email"</string>
     <string name="copy_number" msgid="530057841276106843">"Salin nomor telepon"</string>
     <string name="done" msgid="2356320650733788862">"Kembali"</string>
diff --git a/chips/res/values-it/strings.xml b/chips/res/values-it/strings.xml
index 93d8735..3f87ec0 100644
--- a/chips/res/values-it/strings.xml
+++ b/chips/res/values-it/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"Altri <xliff:g id="COUNT">%1$s</xliff:g>..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Copia indirizzo email"</string>
     <string name="copy_number" msgid="530057841276106843">"Copia numero di telefono"</string>
     <string name="done" msgid="2356320650733788862">"Invio"</string>
diff --git a/chips/res/values-iw/strings.xml b/chips/res/values-iw/strings.xml
index 02fa54f..d7be75a 100644
--- a/chips/res/values-iw/strings.xml
+++ b/chips/res/values-iw/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"עוד <xliff:g id="COUNT">%1$s</xliff:g>..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"העתק כתובת דוא\"ל"</string>
     <string name="copy_number" msgid="530057841276106843">"העתק מספר טלפון"</string>
     <string name="done" msgid="2356320650733788862">"חזור"</string>
diff --git a/chips/res/values-ja/strings.xml b/chips/res/values-ja/strings.xml
index 6419e94..543eb8a 100644
--- a/chips/res/values-ja/strings.xml
+++ b/chips/res/values-ja/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"その他<xliff:g id="COUNT">%1$s</xliff:g>人..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"メールアドレスをコピー"</string>
     <string name="copy_number" msgid="530057841276106843">"電話番号をコピー"</string>
     <string name="done" msgid="2356320650733788862">"戻る"</string>
diff --git a/chips/res/values-ko/strings.xml b/chips/res/values-ko/strings.xml
index e092e7b..7423ce5 100644
--- a/chips/res/values-ko/strings.xml
+++ b/chips/res/values-ko/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g>개 더보기..."</string>
+    <string name="more_string" msgid="8495478259330621990">"<xliff:g id="COUNT">%1$s</xliff:g>명 이상"</string>
     <string name="copy_email" msgid="7869435992461603532">"이메일 주소 복사"</string>
     <string name="copy_number" msgid="530057841276106843">"전화번호 복사"</string>
     <string name="done" msgid="2356320650733788862">"Enter 키"</string>
diff --git a/chips/res/values-lt/strings.xml b/chips/res/values-lt/strings.xml
index 5a45634..e85eba3 100644
--- a/chips/res/values-lt/strings.xml
+++ b/chips/res/values-lt/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"Dar <xliff:g id="COUNT">%1$s</xliff:g>..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+ <xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopijuoti el. pašto adresą"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopijuoti telefono numerį"</string>
     <string name="done" msgid="2356320650733788862">"Grįžti"</string>
diff --git a/chips/res/values-lv/strings.xml b/chips/res/values-lv/strings.xml
index a91842b..f06e4fc 100644
--- a/chips/res/values-lv/strings.xml
+++ b/chips/res/values-lv/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"vēl <xliff:g id="COUNT">%1$s</xliff:g> personas..."</string>
+    <string name="more_string" msgid="8495478259330621990">"Vairāk nekā <xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopēt e-pasta adresi"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopēt tālruņa numuru"</string>
     <string name="done" msgid="2356320650733788862">"Iev. taust."</string>
diff --git a/chips/res/values-ms/strings.xml b/chips/res/values-ms/strings.xml
index d949122..76320f9 100644
--- a/chips/res/values-ms/strings.xml
+++ b/chips/res/values-ms/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> lagi..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Salin alamat e-mel"</string>
     <string name="copy_number" msgid="530057841276106843">"Salin nombor telefon"</string>
     <string name="done" msgid="2356320650733788862">"Kembali"</string>
diff --git a/chips/res/values-nb/strings.xml b/chips/res/values-nb/strings.xml
index e556302..a71348e 100644
--- a/chips/res/values-nb/strings.xml
+++ b/chips/res/values-nb/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> flere"</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopiér e-postadressen"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopiér telefonnummeret"</string>
     <string name="done" msgid="2356320650733788862">"Gå tilbake"</string>
diff --git a/chips/res/values-nl/strings.xml b/chips/res/values-nl/strings.xml
index 31334f2..c4289c6 100644
--- a/chips/res/values-nl/strings.xml
+++ b/chips/res/values-nl/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> andere..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"E-mailadres kopiëren"</string>
     <string name="copy_number" msgid="530057841276106843">"Telefoonnummer kopiëren"</string>
     <string name="done" msgid="2356320650733788862">"Return"</string>
diff --git a/chips/res/values-pl/strings.xml b/chips/res/values-pl/strings.xml
index ff8699f..8746e48 100644
--- a/chips/res/values-pl/strings.xml
+++ b/chips/res/values-pl/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> więcej..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopiuj adres e-mail"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopiuj numer telefonu"</string>
     <string name="done" msgid="2356320650733788862">"Enter"</string>
diff --git a/chips/res/values-pt-rPT/strings.xml b/chips/res/values-pt-rPT/strings.xml
index e53844f..bfbe1ca 100644
--- a/chips/res/values-pt-rPT/strings.xml
+++ b/chips/res/values-pt-rPT/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"Mais <xliff:g id="COUNT">%1$s</xliff:g>..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Copiar endereço de email"</string>
     <string name="copy_number" msgid="530057841276106843">"Copiar número de telefone"</string>
     <string name="done" msgid="2356320650733788862">"Regressar"</string>
diff --git a/chips/res/values-pt/strings.xml b/chips/res/values-pt/strings.xml
index 123daca..58a23e3 100644
--- a/chips/res/values-pt/strings.xml
+++ b/chips/res/values-pt/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"Mais <xliff:g id="COUNT">%1$s</xliff:g>..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Copiar endereço de e-mail"</string>
     <string name="copy_number" msgid="530057841276106843">"Copiar número de telefone"</string>
     <string name="done" msgid="2356320650733788862">"Enter"</string>
diff --git a/chips/res/values-ro/strings.xml b/chips/res/values-ro/strings.xml
index de9b044..6bd8a36 100644
--- a/chips/res/values-ro/strings.xml
+++ b/chips/res/values-ro/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"Încă <xliff:g id="COUNT">%1$s</xliff:g>..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Copiaţi adresa de e-mail"</string>
     <string name="copy_number" msgid="530057841276106843">"Copiaţi numărul de telefon"</string>
     <string name="done" msgid="2356320650733788862">"Enter"</string>
diff --git a/chips/res/values-ru/strings.xml b/chips/res/values-ru/strings.xml
index 5f8f315..0d6a2d7 100644
--- a/chips/res/values-ru/strings.xml
+++ b/chips/res/values-ru/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"еще <xliff:g id="COUNT">%1$s</xliff:g>..."</string>
+    <string name="more_string" msgid="8495478259330621990">"ещё <xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Скопировать адрес эл. почты"</string>
     <string name="copy_number" msgid="530057841276106843">"Скопировать номер телефона"</string>
     <string name="done" msgid="2356320650733788862">"Назад"</string>
diff --git a/chips/res/values-sk/strings.xml b/chips/res/values-sk/strings.xml
index bf27844..155da99 100644
--- a/chips/res/values-sk/strings.xml
+++ b/chips/res/values-sk/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> ďalších..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopírovať e-mailovú adresu"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopírovať telefónne číslo"</string>
     <string name="done" msgid="2356320650733788862">"Enter"</string>
diff --git a/chips/res/values-sl/strings.xml b/chips/res/values-sl/strings.xml
index 228d717..e9877dd 100644
--- a/chips/res/values-sl/strings.xml
+++ b/chips/res/values-sl/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"Še <xliff:g id="COUNT">%1$s</xliff:g> .."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopiranje e-poštnega naslova"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopiranje telefonske številke"</string>
     <string name="done" msgid="2356320650733788862">"Vračalka"</string>
diff --git a/chips/res/values-sr/strings.xml b/chips/res/values-sr/strings.xml
index df8838f..578ca42 100644
--- a/chips/res/values-sr/strings.xml
+++ b/chips/res/values-sr/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"још <xliff:g id="COUNT">%1$s</xliff:g>..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Копирај адресу е-поште"</string>
     <string name="copy_number" msgid="530057841276106843">"Копирај број телефона"</string>
     <string name="done" msgid="2356320650733788862">"Врати"</string>
diff --git a/chips/res/values-sv/strings.xml b/chips/res/values-sv/strings.xml
index c69cb8a..a2a9f40 100644
--- a/chips/res/values-sv/strings.xml
+++ b/chips/res/values-sv/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> till ..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopiera e-postadress"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopiera telefonnummer"</string>
     <string name="done" msgid="2356320650733788862">"Retur"</string>
diff --git a/chips/res/values-sw/strings.xml b/chips/res/values-sw/strings.xml
index eca23b5..8734bc5 100644
--- a/chips/res/values-sw/strings.xml
+++ b/chips/res/values-sw/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> zaidi ..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Nakili anwani ya barua pepe"</string>
     <string name="copy_number" msgid="530057841276106843">"Nakili namba ya simu"</string>
     <string name="done" msgid="2356320650733788862">"Leta"</string>
diff --git a/chips/res/values-th/strings.xml b/chips/res/values-th/strings.xml
index 81aad55..fffafd0 100644
--- a/chips/res/values-th/strings.xml
+++ b/chips/res/values-th/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"อีก <xliff:g id="COUNT">%1$s</xliff:g> รายการ..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"คัดลอกที่อยู่อีเมล"</string>
     <string name="copy_number" msgid="530057841276106843">"คัดลอกหมายเลขโทรศัพท์"</string>
     <string name="done" msgid="2356320650733788862">"ส่งคืน"</string>
diff --git a/chips/res/values-tl/strings.xml b/chips/res/values-tl/strings.xml
index ca7a7a7..db846ca 100644
--- a/chips/res/values-tl/strings.xml
+++ b/chips/res/values-tl/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> higit pa..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopyahin ang email address"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopyahin ang numero ng telepono"</string>
     <string name="done" msgid="2356320650733788862">"Bumalik"</string>
diff --git a/chips/res/values-tr/strings.xml b/chips/res/values-tr/strings.xml
index e214a7e..1e099a4 100644
--- a/chips/res/values-tr/strings.xml
+++ b/chips/res/values-tr/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> tane daha..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"E-posta adresini kopyala"</string>
     <string name="copy_number" msgid="530057841276106843">"Telefon numarasını kopyala"</string>
     <string name="done" msgid="2356320650733788862">"Enter"</string>
diff --git a/chips/res/values-uk/strings.xml b/chips/res/values-uk/strings.xml
index 8700c71..820183e 100644
--- a/chips/res/values-uk/strings.xml
+++ b/chips/res/values-uk/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"Ще <xliff:g id="COUNT">%1$s</xliff:g>..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Копіювати електронну адресу"</string>
     <string name="copy_number" msgid="530057841276106843">"Копіювати номер телефону"</string>
     <string name="done" msgid="2356320650733788862">"Return"</string>
diff --git a/chips/res/values-vi/strings.xml b/chips/res/values-vi/strings.xml
index 9b4b424..f42d837 100644
--- a/chips/res/values-vi/strings.xml
+++ b/chips/res/values-vi/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> người khác..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Sao chép địa chỉ email"</string>
     <string name="copy_number" msgid="530057841276106843">"Sao chép số điện thoại"</string>
     <string name="done" msgid="2356320650733788862">"Quay lại"</string>
diff --git a/chips/res/values-zh-rCN/strings.xml b/chips/res/values-zh-rCN/strings.xml
index bd3d1ef..2283f75 100644
--- a/chips/res/values-zh-rCN/strings.xml
+++ b/chips/res/values-zh-rCN/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"以及其他 <xliff:g id="COUNT">%1$s</xliff:g> 个..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"复制电子邮件地址"</string>
     <string name="copy_number" msgid="530057841276106843">"复制电话号码"</string>
     <string name="done" msgid="2356320650733788862">"上一步"</string>
diff --git a/chips/res/values-zh-rTW/strings.xml b/chips/res/values-zh-rTW/strings.xml
index c9290c6..62d71cf 100644
--- a/chips/res/values-zh-rTW/strings.xml
+++ b/chips/res/values-zh-rTW/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"還有 <xliff:g id="COUNT">%1$s</xliff:g> 位..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g> 人"</string>
     <string name="copy_email" msgid="7869435992461603532">"複製電子郵件地址"</string>
     <string name="copy_number" msgid="530057841276106843">"複製電話號碼"</string>
     <string name="done" msgid="2356320650733788862">"返回"</string>
diff --git a/chips/res/values-zu/strings.xml b/chips/res/values-zu/strings.xml
index 3c0740a..9ae03ed 100644
--- a/chips/res/values-zu/strings.xml
+++ b/chips/res/values-zu/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="more_string" msgid="8971577924280885648">"<xliff:g id="COUNT">%1$s</xliff:g> okunye..."</string>
+    <string name="more_string" msgid="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
     <string name="copy_email" msgid="7869435992461603532">"Kopisha ikheli le-imeyli"</string>
     <string name="copy_number" msgid="530057841276106843">"Kopisha inombolo yefoni"</string>
     <string name="done" msgid="2356320650733788862">"Buyela"</string>
diff --git a/chips/res/values/dimen.xml b/chips/res/values/dimen.xml
index b93555f..98354d2 100644
--- a/chips/res/values/dimen.xml
+++ b/chips/res/values/dimen.xml
@@ -19,4 +19,5 @@
     <dimen name="chip_height">32dip</dimen>
     <dimen name="chip_text_size">14sp</dimen>
     <dimen name="line_spacing_extra">4dip</dimen>
+    <integer name="chips_max_lines">-1</integer>
 </resources>
\ No newline at end of file
diff --git a/chips/res/values/strings.xml b/chips/res/values/strings.xml
index f743ae3..3588ec3 100644
--- a/chips/res/values/strings.xml
+++ b/chips/res/values/strings.xml
@@ -15,7 +15,7 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Text displayed when the recipientedittextview is not focused. Displays the total number of recipients since the field is shrunk to just display a portion -->
-    <string name="more_string"><xliff:g id="count">%1$s</xliff:g> more...</string>
+    <string name="more_string">\u002B<xliff:g id="count">%1$s</xliff:g></string>
 
     <!-- Text displayed when the user long presses on a chip to copy the recipients email address.
          [CHAR LIMIT=200] -->
diff --git a/chips/src/com/android/ex/chips/BaseRecipientAdapter.java b/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
index 2e1491a..71b610e 100644
--- a/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
+++ b/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
@@ -73,12 +73,12 @@
      * The number of extra entries requested to allow for duplicates. Duplicates
      * are removed from the overall result.
      */
-    private static final int ALLOWANCE_FOR_DUPLICATES = 5;
+    static final int ALLOWANCE_FOR_DUPLICATES = 5;
 
     // This is ContactsContract.PRIMARY_ACCOUNT_NAME. Available from ICS as hidden
-    private static final String PRIMARY_ACCOUNT_NAME = "name_for_primary_account";
+    static final String PRIMARY_ACCOUNT_NAME = "name_for_primary_account";
     // This is ContactsContract.PRIMARY_ACCOUNT_TYPE. Available from ICS as hidden
-    private static final String PRIMARY_ACCOUNT_TYPE = "type_for_primary_account";
+    static final String PRIMARY_ACCOUNT_TYPE = "type_for_primary_account";
 
     /** The number of photos cached in this Adapter. */
     private static final int PHOTO_CACHE_SIZE = 20;
@@ -118,7 +118,7 @@
         public static final int PHOTO = 0;
     }
 
-    private static class DirectoryListQuery {
+    protected static class DirectoryListQuery {
 
         public static final Uri URI =
                 Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, "directories");
@@ -204,6 +204,7 @@
             Cursor directoryCursor = null;
 
             if (TextUtils.isEmpty(constraint)) {
+                clearTempEntries();
                 // Return empty results.
                 return results;
             }
@@ -233,8 +234,8 @@
                     }
 
                     // We'll copy this result to mEntry in publicResults() (run in the UX thread).
-                    final List<RecipientEntry> entries = constructEntryList(false,
-                            entryMap, nonAggregatedEntries, existingDestinations);
+                    final List<RecipientEntry> entries = constructEntryList(
+                            entryMap, nonAggregatedEntries);
 
                     // After having local results, check the size of results. If the results are
                     // not enough, we search remote directories, which will take longer time.
@@ -249,7 +250,7 @@
                         directoryCursor = mContentResolver.query(
                                 DirectoryListQuery.URI, DirectoryListQuery.PROJECTION,
                                 null, null, null);
-                        paramsList = setupOtherDirectories(directoryCursor);
+                        paramsList = setupOtherDirectories(mContext, directoryCursor, mAccount);
                     } else {
                         // We don't need to search other directories.
                         paramsList = null;
@@ -278,12 +279,21 @@
             // TODO: Fix it.
             mCurrentConstraint = constraint;
 
+            clearTempEntries();
+
             if (results.values != null) {
                 DefaultFilterResult defaultFilterResult = (DefaultFilterResult) results.values;
                 mEntryMap = defaultFilterResult.entryMap;
                 mNonAggregatedEntries = defaultFilterResult.nonAggregatedEntries;
                 mExistingDestinations = defaultFilterResult.existingDestinations;
 
+                // If there are no local results, in the new result set, cache off what had been
+                // shown to the user for use until the first directory result is returned
+                if (defaultFilterResult.entries.size() == 0 &&
+                        defaultFilterResult.paramsList != null) {
+                    cacheCurrentEntries();
+                }
+
                 updateEntries(defaultFilterResult.entries);
 
                 // We need to search other remote directories, doing other Filter requests.
@@ -404,11 +414,17 @@
                     }
                     mDelayedMessageHandler.sendDelayedLoadMessage();
                 }
+
+                // If this directory result has some items, or there are no more directories that
+                // we are waiting for, clear the temp results
+                if (results.count > 0 || mRemainingDirectoryCount == 0) {
+                    // Clear the temp entries
+                    clearTempEntries();
+                }
             }
 
             // Show the list again without "waiting" message.
-            updateEntries(constructEntryList(false,
-                    mEntryMap, mNonAggregatedEntries, mExistingDestinations));
+            updateEntries(constructEntryList(mEntryMap, mNonAggregatedEntries));
         }
     }
 
@@ -442,6 +458,7 @@
     private Set<String> mExistingDestinations;
     /** Note: use {@link #updateEntries(List)} to update this variable. */
     private List<RecipientEntry> mEntries;
+    private List<RecipientEntry> mTempEntries;
 
     /** The number of directories this adapter is waiting for results. */
     private int mRemainingDirectoryCount;
@@ -464,8 +481,7 @@
         @Override
         public void handleMessage(Message msg) {
             if (mRemainingDirectoryCount > 0) {
-                updateEntries(constructEntryList(true,
-                        mEntryMap, mNonAggregatedEntries, mExistingDestinations));
+                updateEntries(constructEntryList(mEntryMap, mNonAggregatedEntries));
             }
         }
 
@@ -481,6 +497,8 @@
 
     private final DelayedMessageHandler mDelayedMessageHandler = new DelayedMessageHandler();
 
+    private EntriesUpdatedObserver mEntriesUpdatedObserver;
+
     /**
      * Constructor for email queries.
      */
@@ -536,8 +554,9 @@
         return new DefaultFilter();
     }
 
-    private List<DirectorySearchParams> setupOtherDirectories(Cursor directoryCursor) {
-        final PackageManager packageManager = mContext.getPackageManager();
+    public static List<DirectorySearchParams> setupOtherDirectories(Context context,
+            Cursor directoryCursor, Account account) {
+        final PackageManager packageManager = context.getPackageManager();
         final List<DirectorySearchParams> paramsList = new ArrayList<DirectorySearchParams>();
         DirectorySearchParams preferredDirectory = null;
         while (directoryCursor.moveToNext()) {
@@ -574,8 +593,8 @@
             // If an account has been provided and we found a directory that
             // corresponds to that account, place that directory second, directly
             // underneath the local contacts.
-            if (mAccount != null && mAccount.name.equals(params.accountName) &&
-                    mAccount.type.equals(params.accountType)) {
+            if (account != null && account.name.equals(params.accountName) &&
+                    account.type.equals(params.accountType)) {
                 preferredDirectory = params;
             } else {
                 paramsList.add(params);
@@ -613,7 +632,7 @@
         mDelayedMessageHandler.sendDelayedLoadMessage();
     }
 
-    private void putOneEntry(TemporaryEntry entry, boolean isAggregatedEntry,
+    private static void putOneEntry(TemporaryEntry entry, boolean isAggregatedEntry,
             LinkedHashMap<Long, List<RecipientEntry>> entryMap,
             List<RecipientEntry> nonAggregatedEntries,
             Set<String> existingDestinations) {
@@ -628,7 +647,7 @@
                     entry.displayName,
                     entry.displayNameSource,
                     entry.destination, entry.destinationType, entry.destinationLabel,
-                    entry.contactId, entry.dataId, entry.thumbnailUriString));
+                    entry.contactId, entry.dataId, entry.thumbnailUriString, true));
         } else if (entryMap.containsKey(entry.contactId)) {
             // We already have a section for the person.
             final List<RecipientEntry> entryList = entryMap.get(entry.contactId);
@@ -636,14 +655,14 @@
                     entry.displayName,
                     entry.displayNameSource,
                     entry.destination, entry.destinationType, entry.destinationLabel,
-                    entry.contactId, entry.dataId, entry.thumbnailUriString));
+                    entry.contactId, entry.dataId, entry.thumbnailUriString, true));
         } else {
             final List<RecipientEntry> entryList = new ArrayList<RecipientEntry>();
             entryList.add(RecipientEntry.constructTopLevelEntry(
                     entry.displayName,
                     entry.displayNameSource,
                     entry.destination, entry.destinationType, entry.destinationLabel,
-                    entry.contactId, entry.dataId, entry.thumbnailUriString));
+                    entry.contactId, entry.dataId, entry.thumbnailUriString, true));
             entryMap.put(entry.contactId, entryList);
         }
     }
@@ -654,10 +673,8 @@
      * thread to get one from directories.
      */
     private List<RecipientEntry> constructEntryList(
-            boolean showMessageIfDirectoryLoadRemaining,
             LinkedHashMap<Long, List<RecipientEntry>> entryMap,
-            List<RecipientEntry> nonAggregatedEntries,
-            Set<String> existingDestinations) {
+            List<RecipientEntry> nonAggregatedEntries) {
         final List<RecipientEntry> entries = new ArrayList<RecipientEntry>();
         int validEntryCount = 0;
         for (Map.Entry<Long, List<RecipientEntry>> mapEntry : entryMap.entrySet()) {
@@ -688,12 +705,34 @@
         return entries;
     }
 
+
+    protected interface EntriesUpdatedObserver {
+        public void onChanged(List<RecipientEntry> entries);
+    }
+
+    public void registerUpdateObserver(EntriesUpdatedObserver observer) {
+        mEntriesUpdatedObserver = observer;
+    }
+
     /** Resets {@link #mEntries} and notify the event to its parent ListView. */
     private void updateEntries(List<RecipientEntry> newEntries) {
         mEntries = newEntries;
+        mEntriesUpdatedObserver.onChanged(newEntries);
         notifyDataSetChanged();
     }
 
+    private void cacheCurrentEntries() {
+        mTempEntries = mEntries;
+    }
+
+    private void clearTempEntries() {
+        mTempEntries = null;
+    }
+
+    private List<RecipientEntry> getEntries() {
+        return mTempEntries != null ? mTempEntries : mEntries;
+    }
+
     private void tryFetchPhoto(final RecipientEntry entry) {
         final Uri photoThumbnailUri = entry.getPhotoThumbnailUri();
         if (photoThumbnailUri != null) {
@@ -799,12 +838,13 @@
 
     @Override
     public int getCount() {
-        return mEntries != null ? mEntries.size() : 0;
+        final List<RecipientEntry> entries = getEntries();
+        return entries != null ? entries.size() : 0;
     }
 
     @Override
     public Object getItem(int position) {
-        return mEntries.get(position);
+        return getEntries().get(position);
     }
 
     @Override
@@ -819,17 +859,17 @@
 
     @Override
     public int getItemViewType(int position) {
-        return mEntries.get(position).getEntryType();
+        return getEntries().get(position).getEntryType();
     }
 
     @Override
     public boolean isEnabled(int position) {
-        return mEntries.get(position).isSelectable();
+        return getEntries().get(position).isSelectable();
     }
 
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
-        final RecipientEntry entry = mEntries.get(position);
+        final RecipientEntry entry = getEntries().get(position);
         String displayName = entry.getDisplayName();
         String destination = entry.getDestination();
         if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) {
@@ -868,7 +908,7 @@
             if (imageView != null) {
                 imageView.setVisibility(View.VISIBLE);
                 final byte[] photoBytes = entry.getPhotoBytes();
-                if (photoBytes != null && imageView != null) {
+                if (photoBytes != null) {
                     final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
                             photoBytes.length);
                     imageView.setImageBitmap(photo);
@@ -936,4 +976,8 @@
     protected int getPhotoId() {
         return android.R.id.icon;
     }
+
+    public Account getAccount() {
+        return mAccount;
+    }
 }
diff --git a/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java b/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
index 553890e..0693df2 100644
--- a/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
+++ b/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
@@ -16,9 +16,14 @@
 
 package com.android.ex.chips;
 
+import android.accounts.Account;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
 import android.database.MatrixCursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
 import android.text.util.Rfc822Token;
 import android.text.util.Rfc822Tokenizer;
 import android.util.Log;
@@ -29,11 +34,16 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import com.android.ex.chips.BaseRecipientAdapter.DirectoryListQuery;
+import com.android.ex.chips.BaseRecipientAdapter.DirectorySearchParams;
 import com.android.ex.chips.Queries.Query;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * RecipientAlternatesAdapter backs the RecipientEditTextView for managing contacts
@@ -55,9 +65,17 @@
     public static final int QUERY_TYPE_PHONE = 1;
     private Query mQuery;
 
-    public static HashMap<String, RecipientEntry> getMatchingRecipients(Context context,
-            ArrayList<String> inAddresses) {
-        return getMatchingRecipients(context, inAddresses, QUERY_TYPE_EMAIL);
+    public interface RecipientMatchCallback {
+        public void matchesFound(Map<String, RecipientEntry> results);
+        /**
+         * Called with all addresses that could not be resolved to valid recipients.
+         */
+        public void matchesNotFound(Set<String> unfoundAddresses);
+    }
+
+    public static void getMatchingRecipients(Context context, ArrayList<String> inAddresses,
+            Account account, RecipientMatchCallback callback) {
+        getMatchingRecipients(context, inAddresses, QUERY_TYPE_EMAIL, account, callback);
     }
 
     /**
@@ -67,10 +85,11 @@
      *
      * @param context Context.
      * @param inAddresses Array of addresses on which to perform the lookup.
+     * @param callback RecipientMatchCallback called when a match or matches are found.
      * @return HashMap<String,RecipientEntry>
      */
-    public static HashMap<String, RecipientEntry> getMatchingRecipients(Context context,
-            ArrayList<String> inAddresses, int addressType) {
+    public static void getMatchingRecipients(Context context, ArrayList<String> inAddresses,
+            int addressType, Account account, RecipientMatchCallback callback) {
         Queries.Query query;
         if (addressType == QUERY_TYPE_EMAIL) {
             query = Queries.EMAIL;
@@ -78,12 +97,12 @@
             query = Queries.PHONE;
         }
         int addressesSize = Math.min(MAX_LOOKUPS, inAddresses.size());
-        String[] addresses = new String[addressesSize];
+        HashSet<String> addresses = new HashSet<String>();
         StringBuilder bindString = new StringBuilder();
         // Create the "?" string and set up arguments.
         for (int i = 0; i < addressesSize; i++) {
             Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(inAddresses.get(i).toLowerCase());
-            addresses[i] = (tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i));
+            addresses.add(tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i));
             bindString.append("?");
             if (i < addressesSize - 1) {
                 bindString.append(",");
@@ -94,49 +113,205 @@
             Log.d(TAG, "Doing reverse lookup for " + addresses.toString());
         }
 
-        HashMap<String, RecipientEntry> recipientEntries = new HashMap<String, RecipientEntry>();
-        Cursor c = context.getContentResolver().query(
-                query.getContentUri(),
-                query.getProjection(),
-                query.getProjection()[Queries.Query.DESTINATION] + " IN (" + bindString.toString()
-                        + ")", addresses, null);
+        String[] addressArray = new String[addresses.size()];
+        addresses.toArray(addressArray);
+        HashMap<String, RecipientEntry> recipientEntries = null;
+        Cursor c = null;
 
-        if (c != null) {
-            try {
-                if (c.moveToFirst()) {
-                    do {
-                        String address = c.getString(Queries.Query.DESTINATION);
-                        recipientEntries.put(address, RecipientEntry.constructTopLevelEntry(
-                                c.getString(Queries.Query.NAME),
-                                c.getInt(Queries.Query.DISPLAY_NAME_SOURCE),
-                                c.getString(Queries.Query.DESTINATION),
-                                c.getInt(Queries.Query.DESTINATION_TYPE),
-                                c.getString(Queries.Query.DESTINATION_LABEL),
-                                c.getLong(Queries.Query.CONTACT_ID),
-                                c.getLong(Queries.Query.DATA_ID),
-                                c.getString(Queries.Query.PHOTO_THUMBNAIL_URI)));
-                        if (Log.isLoggable(TAG, Log.DEBUG)) {
-                            Log.d(TAG, "Received reverse look up information for " + address
-                                    + " RESULTS: "
-                                    + " NAME : " + c.getString(Queries.Query.NAME)
-                                    + " CONTACT ID : " + c.getLong(Queries.Query.CONTACT_ID)
-                                    + " ADDRESS :" + c.getString(Queries.Query.DESTINATION));
-                        }
-                    } while (c.moveToNext());
-                }
-            } finally {
+        try {
+            c = context.getContentResolver().query(
+                    query.getContentUri(),
+                    query.getProjection(),
+                    query.getProjection()[Queries.Query.DESTINATION] + " IN ("
+                            + bindString.toString() + ")", addressArray, null);
+            recipientEntries = processContactEntries(c);
+            callback.matchesFound(recipientEntries);
+        } finally {
+            if (c != null) {
                 c.close();
             }
         }
+        // See if any entries did not resolve; if so, we need to check other
+        // directories
+        final Set<String> matchesNotFound = new HashSet<String>();
+        if (recipientEntries.size() < addresses.size()) {
+            final List<DirectorySearchParams> paramsList;
+            Cursor directoryCursor = null;
+            try {
+                directoryCursor = context.getContentResolver().query(DirectoryListQuery.URI,
+                        DirectoryListQuery.PROJECTION, null, null, null);
+                paramsList = BaseRecipientAdapter.setupOtherDirectories(context, directoryCursor,
+                        account);
+            } finally {
+                if (directoryCursor != null) {
+                    directoryCursor.close();
+                }
+            }
+            // Run a directory query for each unmatched recipient.
+            HashSet<String> unresolvedAddresses = new HashSet<String>();
+            for (String address : addresses) {
+                if (!recipientEntries.containsKey(address)) {
+                    unresolvedAddresses.add(address);
+                }
+            }
+
+            matchesNotFound.addAll(unresolvedAddresses);
+
+            Cursor directoryContactsCursor = null;
+            for (String unresolvedAddress : unresolvedAddresses) {
+                for (int i = 0; i < paramsList.size(); i++) {
+                    try {
+                        directoryContactsCursor = doQuery(unresolvedAddress, 1,
+                                paramsList.get(i).directoryId, account,
+                                context.getContentResolver(), query);
+                    } finally {
+                        if (directoryContactsCursor != null
+                                && directoryContactsCursor.getCount() == 0) {
+                            directoryContactsCursor.close();
+                            directoryContactsCursor = null;
+                        } else {
+                            break;
+                        }
+                    }
+                }
+                if (directoryContactsCursor != null) {
+                    try {
+                        final Map<String, RecipientEntry> entries =
+                                processContactEntries(directoryContactsCursor);
+
+                        for (final String address : entries.keySet()) {
+                            matchesNotFound.remove(address);
+                        }
+
+                        callback.matchesFound(entries);
+                    } finally {
+                        directoryContactsCursor.close();
+                    }
+                }
+            }
+        }
+
+        callback.matchesNotFound(matchesNotFound);
+    }
+
+    private static HashMap<String, RecipientEntry> processContactEntries(Cursor c) {
+        HashMap<String, RecipientEntry> recipientEntries = new HashMap<String, RecipientEntry>();
+        if (c != null && c.moveToFirst()) {
+            do {
+                String address = c.getString(Queries.Query.DESTINATION);
+
+                final RecipientEntry newRecipientEntry = RecipientEntry.constructTopLevelEntry(
+                        c.getString(Queries.Query.NAME),
+                        c.getInt(Queries.Query.DISPLAY_NAME_SOURCE),
+                        c.getString(Queries.Query.DESTINATION),
+                        c.getInt(Queries.Query.DESTINATION_TYPE),
+                        c.getString(Queries.Query.DESTINATION_LABEL),
+                        c.getLong(Queries.Query.CONTACT_ID),
+                        c.getLong(Queries.Query.DATA_ID),
+                        c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
+                        true);
+
+                /*
+                 * In certain situations, we may have two results for one address, where one of the
+                 * results is just the email address, and the other has a name and photo, so we want
+                 * to use the better one.
+                 */
+                final RecipientEntry recipientEntry =
+                        getBetterRecipient(recipientEntries.get(address), newRecipientEntry);
+
+                recipientEntries.put(address, recipientEntry);
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "Received reverse look up information for " + address
+                            + " RESULTS: "
+                            + " NAME : " + c.getString(Queries.Query.NAME)
+                            + " CONTACT ID : " + c.getLong(Queries.Query.CONTACT_ID)
+                            + " ADDRESS :" + c.getString(Queries.Query.DESTINATION));
+                }
+            } while (c.moveToNext());
+        }
         return recipientEntries;
     }
 
-    public RecipientAlternatesAdapter(Context context, long contactId, long currentId, int viewId,
-            OnCheckedItemChangedListener listener) {
-        this(context, contactId, currentId, viewId, QUERY_TYPE_EMAIL, listener);
+    /**
+     * Given two {@link RecipientEntry}s for the same email address, this will return the one that
+     * contains more complete information for display purposes. Defaults to <code>entry2</code> if
+     * no significant differences are found.
+     */
+    static RecipientEntry getBetterRecipient(final RecipientEntry entry1,
+            final RecipientEntry entry2) {
+        // If only one has passed in, use it
+        if (entry2 == null) {
+            return entry1;
+        }
+
+        if (entry1 == null) {
+            return entry2;
+        }
+
+        // If only one has a display name, use it
+        if (!TextUtils.isEmpty(entry1.getDisplayName())
+                && TextUtils.isEmpty(entry2.getDisplayName())) {
+            return entry1;
+        }
+
+        if (!TextUtils.isEmpty(entry2.getDisplayName())
+                && TextUtils.isEmpty(entry1.getDisplayName())) {
+            return entry2;
+        }
+
+        // If only one has a display name that is not the same as the destination, use it
+        if (!TextUtils.equals(entry1.getDisplayName(), entry1.getDestination())
+                && TextUtils.equals(entry2.getDisplayName(), entry2.getDestination())) {
+            return entry1;
+        }
+
+        if (!TextUtils.equals(entry2.getDisplayName(), entry2.getDestination())
+                && TextUtils.equals(entry1.getDisplayName(), entry1.getDestination())) {
+            return entry2;
+        }
+
+        // If only one has a photo, use it
+        if ((entry1.getPhotoThumbnailUri() != null || entry1.getPhotoBytes() != null)
+                && (entry2.getPhotoThumbnailUri() == null && entry2.getPhotoBytes() == null)) {
+            return entry1;
+        }
+
+        if ((entry2.getPhotoThumbnailUri() != null || entry2.getPhotoBytes() != null)
+                && (entry1.getPhotoThumbnailUri() == null && entry1.getPhotoBytes() == null)) {
+            return entry2;
+        }
+
+        // Go with the second option as a default
+        return entry2;
     }
 
-    public RecipientAlternatesAdapter(Context context, long contactId, long currentId, int viewId,
+    private static Cursor doQuery(CharSequence constraint, int limit, Long directoryId,
+            Account account, ContentResolver resolver, Query query) {
+        final Uri.Builder builder = query
+                .getContentFilterUri()
+                .buildUpon()
+                .appendPath(constraint.toString())
+                .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
+                        String.valueOf(limit + BaseRecipientAdapter.ALLOWANCE_FOR_DUPLICATES));
+        if (directoryId != null) {
+            builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+                    String.valueOf(directoryId));
+        }
+        if (account != null) {
+            builder.appendQueryParameter(BaseRecipientAdapter.PRIMARY_ACCOUNT_NAME, account.name);
+            builder.appendQueryParameter(BaseRecipientAdapter.PRIMARY_ACCOUNT_TYPE, account.type);
+        }
+        final Cursor cursor = resolver.query(builder.build(), query.getProjection(), null, null,
+                null);
+        return cursor;
+    }
+
+    public RecipientAlternatesAdapter(Context context, long contactId, long currentId,
+            OnCheckedItemChangedListener listener) {
+        this(context, contactId, currentId, QUERY_TYPE_EMAIL, listener);
+    }
+
+    public RecipientAlternatesAdapter(Context context, long contactId, long currentId,
             int queryMode, OnCheckedItemChangedListener listener) {
         super(context, getCursorForConstruction(context, contactId, queryMode), 0);
         mLayoutInflater = LayoutInflater.from(context);
@@ -233,7 +408,8 @@
                 c.getString(Queries.Query.DESTINATION_LABEL),
                 c.getLong(Queries.Query.CONTACT_ID),
                 c.getLong(Queries.Query.DATA_ID),
-                c.getString(Queries.Query.PHOTO_THUMBNAIL_URI));
+                c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
+                true);
     }
 
     @Override
diff --git a/chips/src/com/android/ex/chips/RecipientChip.java b/chips/src/com/android/ex/chips/RecipientChip.java
deleted file mode 100644
index 0f93a0d..0000000
--- a/chips/src/com/android/ex/chips/RecipientChip.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2011 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.ex.chips;
-
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.text.style.DynamicDrawableSpan;
-import android.text.style.ImageSpan;
-
-/**
- * RecipientChip defines an ImageSpan that contains information relevant to a
- * particular recipient.
- */
-/* package */class RecipientChip extends ImageSpan {
-    private final CharSequence mDisplay;
-
-    private final CharSequence mValue;
-
-    private final long mContactId;
-
-    private final long mDataId;
-
-    private RecipientEntry mEntry;
-
-    private boolean mSelected = false;
-
-    private CharSequence mOriginalText;
-
-    public RecipientChip(Drawable drawable, RecipientEntry entry, int offset) {
-        super(drawable, DynamicDrawableSpan.ALIGN_BOTTOM);
-        mDisplay = entry.getDisplayName();
-        mValue = entry.getDestination().trim();
-        mContactId = entry.getContactId();
-        mDataId = entry.getDataId();
-        mEntry = entry;
-    }
-
-    /**
-     * Set the selected state of the chip.
-     * @param selected
-     */
-    public void setSelected(boolean selected) {
-        mSelected = selected;
-    }
-
-    /**
-     * Return true if the chip is selected.
-     */
-    public boolean isSelected() {
-        return mSelected;
-    }
-
-    /**
-     * Get the text displayed in the chip.
-     */
-    public CharSequence getDisplay() {
-        return mDisplay;
-    }
-
-    /**
-     * Get the text value this chip represents.
-     */
-    public CharSequence getValue() {
-        return mValue;
-    }
-
-    /**
-     * Get the id of the contact associated with this chip.
-     */
-    public long getContactId() {
-        return mContactId;
-    }
-
-    /**
-     * Get the id of the data associated with this chip.
-     */
-    public long getDataId() {
-        return mDataId;
-    }
-
-    /**
-     * Get associated RecipientEntry.
-     */
-    public RecipientEntry getEntry() {
-        return mEntry;
-    }
-
-    public void setOriginalText(String text) {
-        if (!TextUtils.isEmpty(text)) {
-            text = text.trim();
-        }
-        mOriginalText = text;
-    }
-
-    public CharSequence getOriginalText() {
-        return !TextUtils.isEmpty(mOriginalText) ? mOriginalText : mEntry.getDestination();
-    }
-}
diff --git a/chips/src/com/android/ex/chips/RecipientEditTextView.java b/chips/src/com/android/ex/chips/RecipientEditTextView.java
index dc67901..41bab18 100644
--- a/chips/src/com/android/ex/chips/RecipientEditTextView.java
+++ b/chips/src/com/android/ex/chips/RecipientEditTextView.java
@@ -1,4 +1,5 @@
 /*
+
  * Copyright (C) 2011 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,6 +17,18 @@
 
 package com.android.ex.chips;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 import android.app.Dialog;
 import android.content.ClipData;
 import android.content.ClipDescription;
@@ -36,6 +49,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Parcelable;
 import android.text.Editable;
@@ -43,7 +57,6 @@
 import android.text.Layout;
 import android.text.Spannable;
 import android.text.SpannableString;
-import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.TextPaint;
 import android.text.TextUtils;
@@ -54,7 +67,7 @@
 import android.text.util.Rfc822Tokenizer;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.util.Patterns;
+import android.util.TypedValue;
 import android.view.ActionMode;
 import android.view.ActionMode.Callback;
 import android.view.DragEvent;
@@ -72,6 +85,7 @@
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.Button;
+import android.widget.Filterable;
 import android.widget.ListAdapter;
 import android.widget.ListPopupWindow;
 import android.widget.ListView;
@@ -79,15 +93,10 @@
 import android.widget.ScrollView;
 import android.widget.TextView;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.regex.Matcher;
+import com.android.ex.chips.RecipientAlternatesAdapter.RecipientMatchCallback;
+import com.android.ex.chips.recipientchip.DrawableRecipientChip;
+import com.android.ex.chips.recipientchip.InvisibleRecipientChip;
+import com.android.ex.chips.recipientchip.VisibleRecipientChip;
 
 /**
  * RecipientEditTextView is an auto complete text view for use with applications
@@ -104,6 +113,9 @@
 
     private static final char COMMIT_CHAR_SPACE = ' ';
 
+    private static final String SEPARATOR = String.valueOf(COMMIT_CHAR_COMMA)
+            + String.valueOf(COMMIT_CHAR_SPACE);
+
     private static final String TAG = "RecipientEditTextView";
 
     private static int DISMISS = "dismiss".hashCode();
@@ -139,7 +151,7 @@
 
     private Validator mValidator;
 
-    private RecipientChip mSelectedChip;
+    private DrawableRecipientChip mSelectedChip;
 
     private int mAlternatesLayout;
 
@@ -149,7 +161,8 @@
 
     private TextView mMoreItem;
 
-    private final ArrayList<String> mPendingChips = new ArrayList<String>();
+    // VisibleForTesting
+    final ArrayList<String> mPendingChips = new ArrayList<String>();
 
     private Handler mHandler;
 
@@ -161,9 +174,10 @@
 
     private ListPopupWindow mAddressPopup;
 
-    private ArrayList<RecipientChip> mTemporaryRecipients;
+    // VisibleForTesting
+    ArrayList<DrawableRecipientChip> mTemporaryRecipients;
 
-    private ArrayList<RecipientChip> mRemovedSpans;
+    private ArrayList<DrawableRecipientChip> mRemovedSpans;
 
     private boolean mShouldShrink = true;
 
@@ -192,6 +206,14 @@
 
     private boolean mDragEnabled = false;
 
+    // This pattern comes from android.util.Patterns. It has been tweaked to handle a "1" before
+    // parens, so numbers such as "1 (425) 222-2342" match.
+    private static final Pattern PHONE_PATTERN
+        = Pattern.compile(                                  // sdd = space, dot, or dash
+                "(\\+[0-9]+[\\- \\.]*)?"                    // +<digits><sdd>*
+                + "(1?[ ]*\\([0-9]+\\)[\\- \\.]*)?"         // 1(<digits>)<sdd>*
+                + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
+
     private final Runnable mAddTextWatcher = new Runnable() {
         @Override
         public void run() {
@@ -222,6 +244,12 @@
 
     };
 
+    private int mMaxLines;
+
+    private static int sExcessTopPadding = -1;
+
+    private int mActionBarHeight;
+
     public RecipientEditTextView(Context context, AttributeSet attrs) {
         super(context, attrs);
         setChipDimensions(context, attrs);
@@ -292,13 +320,15 @@
         if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
             outAttrs.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
         }
+
+        outAttrs.actionId = EditorInfo.IME_ACTION_DONE;
         outAttrs.actionLabel = getContext().getString(R.string.done);
         return connection;
     }
 
-    /*package*/ RecipientChip getLastChip() {
-        RecipientChip last = null;
-        RecipientChip[] chips = getSortedRecipients();
+    /*package*/ DrawableRecipientChip getLastChip() {
+        DrawableRecipientChip last = null;
+        DrawableRecipientChip[] chips = getSortedRecipients();
         if (chips != null && chips.length > 0) {
             last = chips[chips.length - 1];
         }
@@ -309,7 +339,7 @@
     public void onSelectionChanged(int start, int end) {
         // When selection changes, see if it is inside the chips area.
         // If so, move the cursor back after the chips again.
-        RecipientChip last = getLastChip();
+        DrawableRecipientChip last = getLastChip();
         if (last != null && start < getSpannable().getSpanEnd(last)) {
             // Grab the last chip and set the cursor to after it.
             setSelection(Math.min(getSpannable().getSpanEnd(last) + 1, getText().length()));
@@ -347,15 +377,22 @@
         }
         super.append(text, start, end);
         if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
-            final String displayString = text.toString();
-            int seperatorPos = displayString.indexOf(COMMIT_CHAR_COMMA);
-            if (seperatorPos != 0 && !TextUtils.isEmpty(displayString)
+            String displayString = text.toString();
+
+            if (!displayString.trim().endsWith(String.valueOf(COMMIT_CHAR_COMMA))) {
+                // We have no separator, so we should add it
+                super.append(SEPARATOR, 0, SEPARATOR.length());
+                displayString += SEPARATOR;
+            }
+
+            if (!TextUtils.isEmpty(displayString)
                     && TextUtils.getTrimmedLength(displayString) > 0) {
                 mPendingChipsCount++;
-                mPendingChips.add(text.toString());
+                mPendingChips.add(displayString);
             }
         }
-        // Put a message on the queue to make sure we ALWAYS handle pending chips.
+        // Put a message on the queue to make sure we ALWAYS handle pending
+        // chips.
         if (mPendingChipsCount > 0) {
             postHandlePendingChips();
         }
@@ -381,8 +418,9 @@
         if (mTokenizer == null) {
             return;
         }
-        if (mSelectedChip != null
-                && mSelectedChip.getEntry().getContactId() != RecipientEntry.INVALID_CONTACT) {
+        long contactId = mSelectedChip != null ? mSelectedChip.getEntry().getContactId() : -1;
+        if (mSelectedChip != null && contactId != RecipientEntry.INVALID_CONTACT
+                && (!isPhoneQuery() && contactId != RecipientEntry.GENERATED_CONTACT)) {
             clearSelectedChip();
         } else {
             if (getWidth() <= 0) {
@@ -403,13 +441,14 @@
                 Editable editable = getText();
                 int end = getSelectionEnd();
                 int start = mTokenizer.findTokenStart(editable, end);
-                RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
+                DrawableRecipientChip[] chips =
+                        getSpannable().getSpans(start, end, DrawableRecipientChip.class);
                 if ((chips == null || chips.length == 0)) {
                     Editable text = getText();
                     int whatEnd = mTokenizer.findTokenEnd(text, start);
                     // This token was already tokenized, so skip past the ending token.
                     if (whatEnd < text.length() && text.charAt(whatEnd) == ',') {
-                        whatEnd++;
+                        whatEnd = movePastTerminators(whatEnd);
                     }
                     // In the middle of chip; treat this as an edit
                     // and commit the whole token.
@@ -427,6 +466,9 @@
     }
 
     private void expand() {
+        if (mShouldShrink) {
+            setMaxLines(Integer.MAX_VALUE);
+        }
         removeMoreChip();
         setCursorVisible(true);
         Editable text = getText();
@@ -448,7 +490,7 @@
                 TextUtils.TruncateAt.END);
     }
 
-    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
+    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint) {
         // Ellipsize the text so that it takes AT MOST the entire width of the
         // autocomplete text entry area. Make sure to leave space for padding
         // on the sides.
@@ -457,7 +499,7 @@
         float[] widths = new float[1];
         paint.getTextWidths(" ", widths);
         CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
-                calculateAvailableWidth(true) - deleteWidth - widths[0]);
+                calculateAvailableWidth() - deleteWidth - widths[0]);
 
         // Make sure there is a minimum chip width so the user can ALWAYS
         // tap a chip without difficulty.
@@ -490,7 +532,8 @@
     }
 
 
-    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint, Layout layout) {
+    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint,
+            boolean leaveBlankIconSpacer) {
         // Ellipsize the text so that it takes AT MOST the entire width of the
         // autocomplete text entry area. Make sure to leave space for padding
         // on the sides.
@@ -499,7 +542,7 @@
         float[] widths = new float[1];
         paint.getTextWidths(" ", widths);
         CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
-                calculateAvailableWidth(false) - iconWidth - widths[0]);
+                calculateAvailableWidth() - iconWidth - widths[0]);
         // Make sure there is a minimum chip width so the user can ALWAYS
         // tap a chip without difficulty.
         int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
@@ -514,8 +557,14 @@
             background.setBounds(0, 0, width, height);
             background.draw(canvas);
 
-            // Don't draw photos for recipients that have been typed in.
-            if (contact.getContactId() != RecipientEntry.INVALID_CONTACT) {
+            // Don't draw photos for recipients that have been typed in OR generated on the fly.
+            long contactId = contact.getContactId();
+            boolean drawPhotos = isPhoneQuery() ?
+                    contactId != RecipientEntry.INVALID_CONTACT
+                    : (contactId != RecipientEntry.INVALID_CONTACT
+                            && (contactId != RecipientEntry.GENERATED_CONTACT &&
+                                    !TextUtils.isEmpty(contact.getDisplayName())));
+            if (drawPhotos) {
                 byte[] photoBytes = contact.getPhotoBytes();
                 // There may not be a photo yet if anything but the first contact address
                 // was selected.
@@ -546,8 +595,7 @@
                     matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
                     canvas.drawBitmap(photo, matrix, paint);
                 }
-            } else {
-                // Don't leave any space for the icon. It isn't being drawn.
+            } else if (!leaveBlankIconSpacer || isPhoneQuery()) {
                 iconWidth = 0;
             }
             paint.setColor(getContext().getResources().getColor(android.R.color.black));
@@ -564,25 +612,23 @@
      * Get the background drawable for a RecipientChip.
      */
     // Visible for testing.
-    /*package*/ Drawable getChipBackground(RecipientEntry contact) {
-        return (mValidator != null && mValidator.isValid(contact.getDestination())) ?
-                mChipBackground : mInvalidChipBackground;
+    /* package */Drawable getChipBackground(RecipientEntry contact) {
+        return contact.isValid() ? mChipBackground : mInvalidChipBackground;
     }
 
-    private float getTextYOffset(String text, TextPaint paint, int height) {
+    private static float getTextYOffset(String text, TextPaint paint, int height) {
         Rect bounds = new Rect();
         paint.getTextBounds(text, 0, text.length(), bounds);
         int textHeight = bounds.bottom - bounds.top ;
         return height - ((height - textHeight) / 2) - (int)paint.descent();
     }
 
-    private RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed)
-            throws NullPointerException {
+    private DrawableRecipientChip constructChipSpan(RecipientEntry contact, boolean pressed,
+            boolean leaveIconSpace) throws NullPointerException {
         if (mChipBackground == null) {
             throw new NullPointerException(
                     "Unable to render any chips as setChipDimensions was not called.");
         }
-        Layout layout = getLayout();
 
         TextPaint paint = getPaint();
         float defaultSize = paint.getTextSize();
@@ -590,16 +636,16 @@
 
         Bitmap tmpBitmap;
         if (pressed) {
-            tmpBitmap = createSelectedChip(contact, paint, layout);
+            tmpBitmap = createSelectedChip(contact, paint);
 
         } else {
-            tmpBitmap = createUnselectedChip(contact, paint, layout);
+            tmpBitmap = createUnselectedChip(contact, paint, leaveIconSpace);
         }
 
         // Pass the full text, un-ellipsized, to the chip.
         Drawable result = new BitmapDrawable(getResources(), tmpBitmap);
         result.setBounds(0, 0, tmpBitmap.getWidth(), tmpBitmap.getHeight());
-        RecipientChip recipientChip = new RecipientChip(result, contact, offset);
+        DrawableRecipientChip recipientChip = new VisibleRecipientChip(result, contact);
         // Return text to the original size.
         paint.setTextSize(defaultSize);
         paint.setColor(defaultColor);
@@ -624,7 +670,7 @@
      * account the width of the EditTextView, any view padding, and padding
      * that will be added to the chip.
      */
-    private float calculateAvailableWidth(boolean pressed) {
+    private float calculateAvailableWidth() {
         return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
     }
 
@@ -633,6 +679,7 @@
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecipientEditTextView, 0,
                 0);
         Resources r = getContext().getResources();
+
         mChipBackground = a.getDrawable(R.styleable.RecipientEditTextView_chipBackground);
         if (mChipBackground == null) {
             mChipBackground = r.getDrawable(R.drawable.chip_background);
@@ -673,7 +720,13 @@
         if (mInvalidChipBackground == null) {
             mInvalidChipBackground = r.getDrawable(R.drawable.chip_background_invalid);
         }
-        mLineSpacingExtra =  context.getResources().getDimension(R.dimen.line_spacing_extra);
+        mLineSpacingExtra =  r.getDimension(R.dimen.line_spacing_extra);
+        mMaxLines = r.getInteger(R.integer.chips_max_lines);
+        TypedValue tv = new TypedValue();
+        if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
+            mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources()
+                    .getDisplayMetrics());
+        }
         a.recycle();
     }
 
@@ -734,11 +787,11 @@
 
     private void checkChipWidths() {
         // Check the widths of the associated chips.
-        RecipientChip[] chips = getSortedRecipients();
+        DrawableRecipientChip[] chips = getSortedRecipients();
         if (chips != null) {
             Rect bounds;
-            for (RecipientChip chip : chips) {
-                bounds = chip.getDrawable().getBounds();
+            for (DrawableRecipientChip chip : chips) {
+                bounds = chip.getBounds();
                 if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) {
                     // Need to redraw that chip.
                     replaceChip(chip, chip.getEntry());
@@ -766,7 +819,8 @@
                 for (int i = 0; i < mPendingChips.size(); i++) {
                     String current = mPendingChips.get(i);
                     int tokenStart = editable.toString().indexOf(current);
-                    int tokenEnd = tokenStart + current.length();
+                    // Always leave a space at the end between tokens.
+                    int tokenEnd = tokenStart + current.length() - 1;
                     if (tokenStart >= 0) {
                         // When we have a valid token, include it with the token
                         // to the left.
@@ -774,7 +828,8 @@
                                 && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
                             tokenEnd++;
                         }
-                        createReplacementChip(tokenStart, tokenEnd, editable);
+                        createReplacementChip(tokenStart, tokenEnd, editable, i < CHIP_LIMIT
+                                || !mShouldShrink);
                     }
                     mPendingChipsCount--;
                 }
@@ -791,9 +846,15 @@
                 } else {
                     // Create the "more" chip
                     mIndividualReplacements = new IndividualReplacementTask();
-                    mIndividualReplacements.execute(new ArrayList<RecipientChip>(
+                    mIndividualReplacements.execute(new ArrayList<DrawableRecipientChip>(
                             mTemporaryRecipients.subList(0, CHIP_LIMIT)));
-
+                    if (mTemporaryRecipients.size() > CHIP_LIMIT) {
+                        mTemporaryRecipients = new ArrayList<DrawableRecipientChip>(
+                                mTemporaryRecipients.subList(CHIP_LIMIT,
+                                        mTemporaryRecipients.size()));
+                    } else {
+                        mTemporaryRecipients = null;
+                    }
                     createMoreChip();
                 }
             } else {
@@ -822,17 +883,16 @@
             return;
         }
         // Find the last chip; eliminate any commit characters after it.
-        RecipientChip[] chips = getSortedRecipients();
+        DrawableRecipientChip[] chips = getSortedRecipients();
+        Spannable spannable = getSpannable();
         if (chips != null && chips.length > 0) {
             int end;
-            ImageSpan lastSpan;
             mMoreChip = getMoreChip();
             if (mMoreChip != null) {
-                lastSpan = mMoreChip;
+                end = spannable.getSpanEnd(mMoreChip);
             } else {
-                lastSpan = getLastChip();
+                end = getSpannable().getSpanEnd(getLastChip());
             }
-            end = getSpannable().getSpanEnd(lastSpan);
             Editable editable = getText();
             int length = editable.length();
             if (length > end) {
@@ -850,41 +910,46 @@
      * Create a chip that represents just the email address of a recipient. At some later
      * point, this chip will be attached to a real contact entry, if one exists.
      */
-    private void createReplacementChip(int tokenStart, int tokenEnd, Editable editable) {
+    // VisibleForTesting
+    void createReplacementChip(int tokenStart, int tokenEnd, Editable editable,
+            boolean visible) {
         if (alreadyHasChip(tokenStart, tokenEnd)) {
             // There is already a chip present at this location.
             // Don't recreate it.
             return;
         }
         String token = editable.toString().substring(tokenStart, tokenEnd);
-        int commitCharIndex = token.trim().lastIndexOf(COMMIT_CHAR_COMMA);
-        if (commitCharIndex == token.length() - 1) {
-            token = token.substring(0, token.length() - 1);
+        final String trimmedToken = token.trim();
+        int commitCharIndex = trimmedToken.lastIndexOf(COMMIT_CHAR_COMMA);
+        if (commitCharIndex == trimmedToken.length() - 1) {
+            token = trimmedToken.substring(0, trimmedToken.length() - 1);
         }
         RecipientEntry entry = createTokenizedEntry(token);
         if (entry != null) {
-            String destText = createAddressText(entry);
-            // Always leave a blank space at the end of a chip.
-            int textLength = destText.length() - 1;
-            SpannableString chipText = new SpannableString(destText);
-            int end = getSelectionEnd();
-            int start = mTokenizer != null ? mTokenizer.findTokenStart(getText(), end) : 0;
-            RecipientChip chip = null;
+            DrawableRecipientChip chip = null;
             try {
                 if (!mNoChips) {
-                    chip = constructChipSpan(entry, start, false);
-                    chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    /*
+                     * leave space for the contact icon if this is not just an
+                     * email address
+                     */
+                    boolean leaveSpace = TextUtils.isEmpty(entry.getDisplayName())
+                            || TextUtils.equals(entry.getDisplayName(),
+                                    entry.getDestination());
+                    chip = visible ?
+                            constructChipSpan(entry, false, leaveSpace)
+                            : new InvisibleRecipientChip(entry);
                 }
             } catch (NullPointerException e) {
                 Log.e(TAG, e.getMessage(), e);
             }
-            editable.replace(tokenStart, tokenEnd, chipText);
+            editable.setSpan(chip, tokenStart, tokenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
             // Add this chip to the list of entries "to replace"
             if (chip != null) {
                 if (mTemporaryRecipients == null) {
-                    mTemporaryRecipients = new ArrayList<RecipientChip>();
+                    mTemporaryRecipients = new ArrayList<DrawableRecipientChip>();
                 }
-                chip.setOriginalText(chipText.toString());
+                chip.setOriginalText(token);
                 mTemporaryRecipients.add(chip);
             }
         }
@@ -898,65 +963,72 @@
             return false;
         }
 
-        Matcher match = Patterns.PHONE.matcher(number);
+        Matcher match = PHONE_PATTERN.matcher(number);
         return match.matches();
     }
 
-    private RecipientEntry createTokenizedEntry(String token) {
+    // VisibleForTesting
+    RecipientEntry createTokenizedEntry(final String token) {
         if (TextUtils.isEmpty(token)) {
             return null;
         }
         if (isPhoneQuery() && isPhoneNumber(token)) {
-            return RecipientEntry
-                    .constructFakeEntry(token);
+            return RecipientEntry.constructFakePhoneEntry(token, true);
         }
         Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
         String display = null;
-        if (isValid(token) && tokens != null && tokens.length > 0) {
+        boolean isValid = isValid(token);
+        if (isValid && tokens != null && tokens.length > 0) {
             // If we can get a name from tokenizing, then generate an entry from
             // this.
             display = tokens[0].getName();
             if (!TextUtils.isEmpty(display)) {
-                return RecipientEntry.constructGeneratedEntry(display, token);
+                return RecipientEntry.constructGeneratedEntry(display, tokens[0].getAddress(),
+                        isValid);
             } else {
                 display = tokens[0].getAddress();
                 if (!TextUtils.isEmpty(display)) {
-                    return RecipientEntry.constructFakeEntry(display);
+                    return RecipientEntry.constructFakeEntry(display, isValid);
                 }
             }
         }
         // Unable to validate the token or to create a valid token from it.
         // Just create a chip the user can edit.
         String validatedToken = null;
-        if (mValidator != null && !mValidator.isValid(token)) {
+        if (mValidator != null && !isValid) {
             // Try fixing up the entry using the validator.
             validatedToken = mValidator.fixText(token).toString();
             if (!TextUtils.isEmpty(validatedToken)) {
                 if (validatedToken.contains(token)) {
-                    // protect against the case of a validator with a null domain,
+                    // protect against the case of a validator with a null
+                    // domain,
                     // which doesn't add a domain to the token
                     Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(validatedToken);
                     if (tokenized.length > 0) {
                         validatedToken = tokenized[0].getAddress();
+                        isValid = true;
                     }
                 } else {
-                    // We ran into a case where the token was invalid and removed
-                    // by the validator. In this case, just use the original token
+                    // We ran into a case where the token was invalid and
+                    // removed
+                    // by the validator. In this case, just use the original
+                    // token
                     // and let the user sort out the error chip.
                     validatedToken = null;
+                    isValid = false;
                 }
             }
         }
         // Otherwise, fallback to just creating an editable email address chip.
-        return RecipientEntry
-                .constructFakeEntry(!TextUtils.isEmpty(validatedToken) ? validatedToken : token);
+        return RecipientEntry.constructFakeEntry(
+                !TextUtils.isEmpty(validatedToken) ? validatedToken : token, isValid);
     }
 
     private boolean isValid(String text) {
         return mValidator == null ? true : mValidator.isValid(text);
     }
 
-    private String tokenizeAddress(String destination) {
+    private static String tokenizeAddress(String destination) {
         Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
         if (tokens != null && tokens.length > 0) {
             return tokens[0].getAddress();
@@ -1009,20 +1081,6 @@
     @Override
     public boolean onKeyUp(int keyCode, KeyEvent event) {
         switch (keyCode) {
-            case KeyEvent.KEYCODE_ENTER:
-            case KeyEvent.KEYCODE_DPAD_CENTER:
-                if (event.hasNoModifiers()) {
-                    if (commitDefault()) {
-                        return true;
-                    }
-                    if (mSelectedChip != null) {
-                        clearSelectedChip();
-                        return true;
-                    } else if (focusNext()) {
-                        return true;
-                    }
-                }
-                break;
             case KeyEvent.KEYCODE_TAB:
                 if (event.hasNoModifiers()) {
                     if (mSelectedChip != null) {
@@ -1030,9 +1088,6 @@
                     } else {
                         commitDefault();
                     }
-                    if (focusNext()) {
-                        return true;
-                    }
                 }
                 break;
         }
@@ -1068,6 +1123,7 @@
             int whatEnd = mTokenizer.findTokenEnd(getText(), start);
             // In the middle of chip; treat this as an edit
             // and commit the whole token.
+            whatEnd = movePastTerminators(whatEnd);
             if (whatEnd != getSelectionEnd()) {
                 handleEdit(start, whatEnd);
                 return true;
@@ -1139,10 +1195,10 @@
             return;
         }
         // Find the last chip.
-        RecipientChip[] recips = getSortedRecipients();
+        DrawableRecipientChip[] recips = getSortedRecipients();
         if (recips != null && recips.length > 0) {
-            RecipientChip last = recips[recips.length - 1];
-            RecipientChip beforeLast = null;
+            DrawableRecipientChip last = recips[recips.length - 1];
+            DrawableRecipientChip beforeLast = null;
             if (recips.length > 1) {
                 beforeLast = recips[recips.length - 2];
             }
@@ -1173,7 +1229,8 @@
         if (mNoChips) {
             return true;
         }
-        RecipientChip[] chips = getSpannable().getSpans(start, end, RecipientChip.class);
+        DrawableRecipientChip[] chips =
+                getSpannable().getSpans(start, end, DrawableRecipientChip.class);
         if ((chips == null || chips.length == 0)) {
             return false;
         }
@@ -1192,7 +1249,7 @@
         setSelection(end);
         String text = getText().toString().substring(start, end);
         if (!TextUtils.isEmpty(text)) {
-            RecipientEntry entry = RecipientEntry.constructFakeEntry(text);
+            RecipientEntry entry = RecipientEntry.constructFakeEntry(text, isValid(text));
             QwertyKeyListener.markAsReplaced(editable, start, end, "");
             CharSequence chipText = createChip(entry, false);
             int selEnd = getSelectionEnd();
@@ -1216,8 +1273,21 @@
             removeChip(mSelectedChip);
         }
 
-        if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) {
-            return true;
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_ENTER:
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                if (event.hasNoModifiers()) {
+                    if (commitDefault()) {
+                        return true;
+                    }
+                    if (mSelectedChip != null) {
+                        clearSelectedChip();
+                        return true;
+                    } else if (focusNext()) {
+                        return true;
+                    }
+                }
+                break;
         }
 
         return super.onKeyDown(keyCode, event);
@@ -1228,11 +1298,11 @@
         return getText();
     }
 
-    private int getChipStart(RecipientChip chip) {
+    private int getChipStart(DrawableRecipientChip chip) {
         return getSpannable().getSpanStart(chip);
     }
 
-    private int getChipEnd(RecipientChip chip) {
+    private int getChipEnd(DrawableRecipientChip chip) {
         return getSpannable().getSpanEnd(chip);
     }
 
@@ -1245,16 +1315,19 @@
      */
     @Override
     protected void performFiltering(CharSequence text, int keyCode) {
-        if (enoughToFilter() && !isCompletedToken(text)) {
+        boolean isCompletedToken = isCompletedToken(text);
+        if (enoughToFilter() && !isCompletedToken) {
             int end = getSelectionEnd();
             int start = mTokenizer.findTokenStart(text, end);
             // If this is a RecipientChip, don't filter
             // on its contents.
             Spannable span = getSpannable();
-            RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class);
+            DrawableRecipientChip[] chips = span.getSpans(start, end, DrawableRecipientChip.class);
             if (chips != null && chips.length > 0) {
                 return;
             }
+        } else if (isCompletedToken) {
+            return;
         }
         super.performFiltering(text, keyCode);
     }
@@ -1307,7 +1380,7 @@
             float x = event.getX();
             float y = event.getY();
             int offset = putOffsetInRange(getOffsetForPosition(x, y));
-            RecipientChip currentChip = findChip(offset);
+            DrawableRecipientChip currentChip = findChip(offset);
             if (currentChip != null) {
                 if (action == MotionEvent.ACTION_UP) {
                     if (mSelectedChip != null && mSelectedChip != currentChip) {
@@ -1323,8 +1396,7 @@
                 }
                 chipWasSelected = true;
                 handled = true;
-            } else if (mSelectedChip != null
-                    && mSelectedChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
+            } else if (mSelectedChip != null && shouldShowEditableText(mSelectedChip)) {
                 chipWasSelected = true;
             }
         }
@@ -1336,48 +1408,57 @@
 
     private void scrollLineIntoView(int line) {
         if (mScrollView != null) {
-            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
+            mScrollView.smoothScrollBy(0, calculateOffsetFromBottom(line));
         }
     }
 
-    private void showAlternates(RecipientChip currentChip, ListPopupWindow alternatesPopup,
-            int width, Context context) {
-        int line = getLayout().getLineForOffset(getChipStart(currentChip));
-        int bottom;
-        if (line == getLineCount() -1) {
-            bottom = 0;
-        } else {
-            bottom = -(int) ((mChipHeight + (2 * mLineSpacingExtra)) * (Math.abs(getLineCount() - 1
-                    - line)));
-        }
-        // Align the alternates popup with the left side of the View,
-        // regardless of the position of the chip tapped.
-        alternatesPopup.setWidth(width);
-        alternatesPopup.setAnchorView(this);
-        alternatesPopup.setVerticalOffset(bottom);
-        alternatesPopup.setAdapter(createAlternatesAdapter(currentChip));
-        alternatesPopup.setOnItemClickListener(mAlternatesListener);
-        // Clear the checked item.
-        mCheckedItem = -1;
-        alternatesPopup.show();
-        ListView listView = alternatesPopup.getListView();
-        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
-        // Checked item would be -1 if the adapter has not
-        // loaded the view that should be checked yet. The
-        // variable will be set correctly when onCheckedItemChanged
-        // is called in a separate thread.
-        if (mCheckedItem != -1) {
-            listView.setItemChecked(mCheckedItem, true);
-            mCheckedItem = -1;
-        }
+    private void showAlternates(final DrawableRecipientChip currentChip,
+            final ListPopupWindow alternatesPopup, final int width) {
+        new AsyncTask<Void, Void, ListAdapter>() {
+            @Override
+            protected ListAdapter doInBackground(final Void... params) {
+                return createAlternatesAdapter(currentChip);
+            }
+
+            protected void onPostExecute(final ListAdapter result) {
+                int line = getLayout().getLineForOffset(getChipStart(currentChip));
+                int bottom;
+                if (line == getLineCount() -1) {
+                    bottom = 0;
+                } else {
+                    bottom = -(int) ((mChipHeight + (2 * mLineSpacingExtra)) * (Math
+                            .abs(getLineCount() - 1 - line)));
+                }
+                // Align the alternates popup with the left side of the View,
+                // regardless of the position of the chip tapped.
+                alternatesPopup.setWidth(width);
+                alternatesPopup.setAnchorView(RecipientEditTextView.this);
+                alternatesPopup.setVerticalOffset(bottom);
+                alternatesPopup.setAdapter(result);
+                alternatesPopup.setOnItemClickListener(mAlternatesListener);
+                // Clear the checked item.
+                mCheckedItem = -1;
+                alternatesPopup.show();
+                ListView listView = alternatesPopup.getListView();
+                listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+                // Checked item would be -1 if the adapter has not
+                // loaded the view that should be checked yet. The
+                // variable will be set correctly when onCheckedItemChanged
+                // is called in a separate thread.
+                if (mCheckedItem != -1) {
+                    listView.setItemChecked(mCheckedItem, true);
+                    mCheckedItem = -1;
+                }
+            }
+        }.execute((Void[]) null);
     }
 
-    private ListAdapter createAlternatesAdapter(RecipientChip chip) {
+    private ListAdapter createAlternatesAdapter(DrawableRecipientChip chip) {
         return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
-                mAlternatesLayout, ((BaseRecipientAdapter)getAdapter()).getQueryType(), this);
+                ((BaseRecipientAdapter)getAdapter()).getQueryType(), this);
     }
 
-    private ListAdapter createSingleAddressAdapter(RecipientChip currentChip) {
+    private ListAdapter createSingleAddressAdapter(DrawableRecipientChip currentChip) {
         return new SingleRecipientArrayAdapter(getContext(), mAlternatesLayout, currentChip
                 .getEntry());
     }
@@ -1421,18 +1502,19 @@
         return offset;
     }
 
-    private int findText(Editable text, int offset) {
+    private static int findText(Editable text, int offset) {
         if (text.charAt(offset) != ' ') {
             return offset;
         }
         return -1;
     }
 
-    private RecipientChip findChip(int offset) {
-        RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class);
+    private DrawableRecipientChip findChip(int offset) {
+        DrawableRecipientChip[] chips =
+                getSpannable().getSpans(0, getText().length(), DrawableRecipientChip.class);
         // Find the chip that contains this offset.
         for (int i = 0; i < chips.length; i++) {
-            RecipientChip chip = chips[i];
+            DrawableRecipientChip chip = chips[i];
             int start = getChipStart(chip);
             int end = getChipEnd(chip);
             if (offset >= start && offset <= end) {
@@ -1479,14 +1561,6 @@
         if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
             display = null;
         }
-        if (address != null && !(isPhoneQuery() && isPhoneNumber(address))) {
-            // Tokenize out the address in case the address already
-            // contained the username as well.
-            Rfc822Token[] tokenized = Rfc822Tokenizer.tokenize(address);
-            if (tokenized != null && tokenized.length > 0) {
-                address = tokenized[0].getAddress();
-            }
-        }
         if (!TextUtils.isEmpty(display)) {
             return display;
         } else if (!TextUtils.isEmpty(address)){
@@ -1503,13 +1577,12 @@
         }
         SpannableString chipText = null;
         // Always leave a blank space at the end of a chip.
-        int end = getSelectionEnd();
-        int start = mTokenizer.findTokenStart(getText(), end);
-        int textLength = displayText.length()-1;
+        int textLength = displayText.length() - 1;
         chipText = new SpannableString(displayText);
         if (!mNoChips) {
             try {
-                RecipientChip chip = constructChipSpan(entry, start, pressed);
+                DrawableRecipientChip chip = constructChipSpan(entry, pressed,
+                        false /* leave space for contact icon */);
                 chipText.setSpan(chip, 0, textLength,
                         Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                 chip.setOriginalText(chipText.toString());
@@ -1559,11 +1632,14 @@
         // valid contact, but the destination is invalid, then make this a fake
         // recipient that is editable.
         String destination = item.getDestination();
-        if (RecipientEntry.isCreatedRecipient(item.getContactId())
+        if (!isPhoneQuery() && item.getContactId() == RecipientEntry.GENERATED_CONTACT) {
+            entry = RecipientEntry.constructGeneratedEntry(item.getDisplayName(),
+                    destination, item.isValid());
+        } else if (RecipientEntry.isCreatedRecipient(item.getContactId())
                 && (TextUtils.isEmpty(item.getDisplayName())
                         || TextUtils.equals(item.getDisplayName(), destination)
                         || (mValidator != null && !mValidator.isValid(destination)))) {
-            entry = RecipientEntry.constructFakeEntry(destination);
+            entry = RecipientEntry.constructFakeEntry(destination, item.isValid());
         } else {
             entry = item;
         }
@@ -1573,9 +1649,9 @@
     /** Returns a collection of contact Id for each chip inside this View. */
     /* package */ Collection<Long> getContactIds() {
         final Set<Long> result = new HashSet<Long>();
-        RecipientChip[] chips = getSortedRecipients();
+        DrawableRecipientChip[] chips = getSortedRecipients();
         if (chips != null) {
-            for (RecipientChip chip : chips) {
+            for (DrawableRecipientChip chip : chips) {
                 result.add(chip.getContactId());
             }
         }
@@ -1586,9 +1662,9 @@
     /** Returns a collection of data Id for each chip inside this View. May be null. */
     /* package */ Collection<Long> getDataIds() {
         final Set<Long> result = new HashSet<Long>();
-        RecipientChip [] chips = getSortedRecipients();
+        DrawableRecipientChip [] chips = getSortedRecipients();
         if (chips != null) {
-            for (RecipientChip chip : chips) {
+            for (DrawableRecipientChip chip : chips) {
                 result.add(chip.getDataId());
             }
         }
@@ -1596,16 +1672,16 @@
     }
 
     // Visible for testing.
-    /* package */RecipientChip[] getSortedRecipients() {
-        RecipientChip[] recips = getSpannable()
-                .getSpans(0, getText().length(), RecipientChip.class);
-        ArrayList<RecipientChip> recipientsList = new ArrayList<RecipientChip>(Arrays
-                .asList(recips));
+    /* package */DrawableRecipientChip[] getSortedRecipients() {
+        DrawableRecipientChip[] recips = getSpannable()
+                .getSpans(0, getText().length(), DrawableRecipientChip.class);
+        ArrayList<DrawableRecipientChip> recipientsList = new ArrayList<DrawableRecipientChip>(
+                Arrays.asList(recips));
         final Spannable spannable = getSpannable();
-        Collections.sort(recipientsList, new Comparator<RecipientChip>() {
+        Collections.sort(recipientsList, new Comparator<DrawableRecipientChip>() {
 
             @Override
-            public int compare(RecipientChip first, RecipientChip second) {
+            public int compare(DrawableRecipientChip first, DrawableRecipientChip second) {
                 int firstStart = spannable.getSpanStart(first);
                 int secondStart = spannable.getSpanStart(second);
                 if (firstStart < secondStart) {
@@ -1617,7 +1693,7 @@
                 }
             }
         });
-        return recipientsList.toArray(new RecipientChip[recipientsList.size()]);
+        return recipientsList.toArray(new DrawableRecipientChip[recipientsList.size()]);
     }
 
     @Override
@@ -1720,12 +1796,11 @@
         if (!mShouldShrink) {
             return;
         }
-
         ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
         if (tempMore.length > 0) {
             getSpannable().removeSpan(tempMore[0]);
         }
-        RecipientChip[] recipients = getSortedRecipients();
+        DrawableRecipientChip[] recipients = getSortedRecipients();
 
         if (recipients == null || recipients.length <= CHIP_LIMIT) {
             mMoreChip = null;
@@ -1735,7 +1810,7 @@
         int numRecipients = recipients.length;
         int overage = numRecipients - CHIP_LIMIT;
         MoreImageSpan moreSpan = createMoreSpan(overage);
-        mRemovedSpans = new ArrayList<RecipientChip>();
+        mRemovedSpans = new ArrayList<DrawableRecipientChip>();
         int totalReplaceStart = 0;
         int totalReplaceEnd = 0;
         Editable text = getText();
@@ -1763,6 +1838,10 @@
         chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
         text.replace(start, end, chipText);
         mMoreChip = moreSpan;
+        // If adding the +more chip goes over the limit, resize accordingly.
+        if (!isPhoneQuery() && getLineCount() > mMaxLines) {
+            setMaxLines(getLineCount());
+        }
     }
 
     /**
@@ -1778,7 +1857,7 @@
             // Re-add the spans that were removed.
             if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
                 // Recreate each removed span.
-                RecipientChip[] recipients = getSortedRecipients();
+                DrawableRecipientChip[] recipients = getSortedRecipients();
                 // Start the search for tokens after the last currently visible
                 // chip.
                 if (recipients == null || recipients.length == 0) {
@@ -1786,7 +1865,7 @@
                 }
                 int end = span.getSpanEnd(recipients[recipients.length - 1]);
                 Editable editable = getText();
-                for (RecipientChip chip : mRemovedSpans) {
+                for (DrawableRecipientChip chip : mRemovedSpans) {
                     int chipStart;
                     int chipEnd;
                     String token;
@@ -1821,25 +1900,31 @@
      * @return A RecipientChip in the selected state or null if the chip
      * just contained an email address.
      */
-    private RecipientChip selectChip(RecipientChip currentChip) {
-        if (currentChip.getContactId() == RecipientEntry.INVALID_CONTACT) {
+    private DrawableRecipientChip selectChip(DrawableRecipientChip currentChip) {
+        if (shouldShowEditableText(currentChip)) {
             CharSequence text = currentChip.getValue();
             Editable editable = getText();
-            removeChip(currentChip);
-            editable.append(text);
+            Spannable spannable = getSpannable();
+            int spanStart = spannable.getSpanStart(currentChip);
+            int spanEnd = spannable.getSpanEnd(currentChip);
+            spannable.removeSpan(currentChip);
+            editable.delete(spanStart, spanEnd);
             setCursorVisible(true);
             setSelection(editable.length());
-            return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
+            editable.append(text);
+            return constructChipSpan(
+                    RecipientEntry.constructFakeEntry((String) text, isValid(text.toString())),
+                    true, false);
         } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
             int start = getChipStart(currentChip);
             int end = getChipEnd(currentChip);
             getSpannable().removeSpan(currentChip);
-            RecipientChip newChip;
+            DrawableRecipientChip newChip;
             try {
                 if (mNoChips) {
                     return null;
                 }
-                newChip = constructChipSpan(currentChip.getEntry(), start, true);
+                newChip = constructChipSpan(currentChip.getEntry(), true, false);
             } catch (NullPointerException e) {
                 Log.e(TAG, e.getMessage(), e);
                 return null;
@@ -1852,19 +1937,19 @@
                 editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
             }
             newChip.setSelected(true);
-            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
+            if (shouldShowEditableText(newChip)) {
                 scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
             }
-            showAddress(newChip, mAddressPopup, getWidth(), getContext());
+            showAddress(newChip, mAddressPopup, getWidth());
             setCursorVisible(false);
             return newChip;
         } else {
             int start = getChipStart(currentChip);
             int end = getChipEnd(currentChip);
             getSpannable().removeSpan(currentChip);
-            RecipientChip newChip;
+            DrawableRecipientChip newChip;
             try {
-                newChip = constructChipSpan(currentChip.getEntry(), start, true);
+                newChip = constructChipSpan(currentChip.getEntry(), true, false);
             } catch (NullPointerException e) {
                 Log.e(TAG, e.getMessage(), e);
                 return null;
@@ -1877,18 +1962,23 @@
                 editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
             }
             newChip.setSelected(true);
-            if (newChip.getEntry().getContactId() == RecipientEntry.INVALID_CONTACT) {
+            if (shouldShowEditableText(newChip)) {
                 scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
             }
-            showAlternates(newChip, mAlternatesPopup, getWidth(), getContext());
+            showAlternates(newChip, mAlternatesPopup, getWidth());
             setCursorVisible(false);
             return newChip;
         }
     }
 
+    private boolean shouldShowEditableText(DrawableRecipientChip currentChip) {
+        long contactId = currentChip.getContactId();
+        return contactId == RecipientEntry.INVALID_CONTACT
+                || (!isPhoneQuery() && contactId == RecipientEntry.GENERATED_CONTACT);
+    }
 
-    private void showAddress(final RecipientChip currentChip, final ListPopupWindow popup,
-            int width, Context context) {
+    private void showAddress(final DrawableRecipientChip currentChip, final ListPopupWindow popup,
+            int width) {
         int line = getLayout().getLineForOffset(getChipStart(currentChip));
         int bottom = calculateOffsetFromBottom(line);
         // Align the alternates popup with the left side of the View,
@@ -1915,7 +2005,7 @@
      * the chip without a delete icon and with an unfocused background. This is
      * called when the RecipientChip no longer has focus.
      */
-    private void unselectChip(RecipientChip chip) {
+    private void unselectChip(DrawableRecipientChip chip) {
         int start = getChipStart(chip);
         int end = getChipEnd(chip);
         Editable editable = getText();
@@ -1930,8 +2020,8 @@
             editable.removeSpan(chip);
             try {
                 if (!mNoChips) {
-                    editable.setSpan(constructChipSpan(chip.getEntry(), start, false), start, end,
-                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    editable.setSpan(constructChipSpan(chip.getEntry(), false, false),
+                            start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                 }
             } catch (NullPointerException e) {
                 Log.e(TAG, e.getMessage(), e);
@@ -1953,9 +2043,10 @@
      * right after the selected chip.
      * @return boolean
      */
-    private boolean isInDelete(RecipientChip chip, int offset, float x, float y) {
+    private boolean isInDelete(DrawableRecipientChip chip, int offset, float x, float y) {
         // Figure out the bounds of this chip and whether or not
         // the user clicked in the X portion.
+        // TODO: Should x and y be used, or removed?
         return chip.isSelected() && offset == getChipEnd(chip);
     }
 
@@ -1963,7 +2054,7 @@
      * Remove the chip and any text associated with it from the RecipientEditTextView.
      */
     // Visible for testing.
-    /*pacakge*/ void removeChip(RecipientChip chip) {
+    /*pacakge*/ void removeChip(DrawableRecipientChip chip) {
         Spannable spannable = getSpannable();
         int spanStart = spannable.getSpanStart(chip);
         int spanEnd = spannable.getSpanEnd(chip);
@@ -1992,7 +2083,7 @@
      * that uses the contact data provided.
      */
     // Visible for testing.
-    /*package*/ void replaceChip(RecipientChip chip, RecipientEntry entry) {
+    /*package*/ void replaceChip(DrawableRecipientChip chip, RecipientEntry entry) {
         boolean wasSelected = chip == mSelectedChip;
         if (wasSelected) {
             mSelectedChip = null;
@@ -2009,8 +2100,7 @@
             } else {
                 if (!TextUtils.isEmpty(chipText)) {
                     // There may be a space to replace with this chip's new
-                    // associated
-                    // space. Check for it
+                    // associated space. Check for it
                     int toReplace = end;
                     while (toReplace >= 0 && toReplace < editable.length()
                             && editable.charAt(toReplace) == ' ') {
@@ -2031,7 +2121,7 @@
      * event, see if that event was in the delete icon. If so, delete it.
      * Otherwise, unselect the chip.
      */
-    public void onClick(RecipientChip chip, int offset, float x, float y) {
+    public void onClick(DrawableRecipientChip chip, int offset, float x, float y) {
         if (chip.isSelected()) {
             if (isInDelete(chip, offset, x, y)) {
                 removeChip(chip);
@@ -2060,9 +2150,9 @@
             if (TextUtils.isEmpty(s)) {
                 // Remove all the chips spans.
                 Spannable spannable = getSpannable();
-                RecipientChip[] chips = spannable.getSpans(0, getText().length(),
-                        RecipientChip.class);
-                for (RecipientChip chip : chips) {
+                DrawableRecipientChip[] chips = spannable.getSpans(0, getText().length(),
+                        DrawableRecipientChip.class);
+                for (DrawableRecipientChip chip : chips) {
                     spannable.removeSpan(chip);
                 }
                 if (mMoreChip != null) {
@@ -2076,16 +2166,23 @@
                 return;
             }
             // If the user is editing a chip, don't clear it.
-            if (mSelectedChip != null
-                    && mSelectedChip.getContactId() != RecipientEntry.INVALID_CONTACT) {
-                setCursorVisible(true);
-                setSelection(getText().length());
-                clearSelectedChip();
+            if (mSelectedChip != null) {
+                if (!isGeneratedContact(mSelectedChip)) {
+                    setCursorVisible(true);
+                    setSelection(getText().length());
+                    clearSelectedChip();
+                } else {
+                    return;
+                }
             }
             int length = s.length();
             // Make sure there is content there to parse and that it is
             // not just the commit character.
             if (length > 1) {
+                if (lastCharacterIsCommitCharacter(s)) {
+                    commitByCharacter();
+                    return;
+                }
                 char last;
                 int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
                 int len = length() - 1;
@@ -2094,9 +2191,7 @@
                 } else {
                     last = s.charAt(len);
                 }
-                if (last == COMMIT_CHAR_SEMICOLON || last == COMMIT_CHAR_COMMA) {
-                    commitByCharacter();
-                } else if (last == COMMIT_CHAR_SPACE) {
+                if (last == COMMIT_CHAR_SPACE) {
                     if (!isPhoneQuery()) {
                         // Check if this is a valid email address. If it is,
                         // commit it.
@@ -2115,14 +2210,15 @@
 
         @Override
         public void onTextChanged(CharSequence s, int start, int before, int count) {
-            // This is a delete; check to see if the insertion point is on a space
+            // The user deleted some text OR some text was replaced; check to
+            // see if the insertion point is on a space
             // following a chip.
-            if (before > count) {
+            if (before - count == 1) {
                 // If the item deleted is a space, and the thing before the
                 // space is a chip, delete the entire span.
                 int selStart = getSelectionStart();
-                RecipientChip[] repl = getSpannable().getSpans(selStart, selStart,
-                        RecipientChip.class);
+                DrawableRecipientChip[] repl = getSpannable().getSpans(selStart, selStart,
+                        DrawableRecipientChip.class);
                 if (repl.length > 0) {
                     // There is a chip there! Just remove it.
                     Editable editable = getText();
@@ -2136,8 +2232,14 @@
                     editable.delete(tokenStart, tokenEnd);
                     getSpannable().removeSpan(repl[0]);
                 }
-            } else {
-                scrollBottomIntoView();
+            } else if (count > before) {
+                if (mSelectedChip != null
+                    && isGeneratedContact(mSelectedChip)) {
+                    if (lastCharacterIsCommitCharacter(s)) {
+                        commitByCharacter();
+                        return;
+                    }
+                }
             }
         }
 
@@ -2147,12 +2249,62 @@
         }
     }
 
+    @Override
+    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
+        super.setAdapter(adapter);
+        ((BaseRecipientAdapter) adapter)
+                .registerUpdateObserver(new BaseRecipientAdapter.EntriesUpdatedObserver() {
+                    @Override
+                    public void onChanged(List<RecipientEntry> entries) {
+                        if (entries != null && entries.size() > 0) {
+                            scrollBottomIntoView();
+                        }
+                    }
+                });
+    }
+
     private void scrollBottomIntoView() {
-        if (mScrollView != null) {
-            mScrollView.scrollBy(0, (int)(getLineCount() * mChipHeight));
+        if (mScrollView != null && mShouldShrink) {
+            int[] location = new int[2];
+            getLocationOnScreen(location);
+            int height = getHeight();
+            int currentPos = location[1] + height;
+            // Desired position shows at least 1 line of chips below the action
+            // bar.
+            // We add excess padding to make sure this is always below other
+            // content.
+            int desiredPos = (int) mChipHeight + mActionBarHeight + getExcessTopPadding();
+            if (currentPos > desiredPos) {
+                mScrollView.scrollBy(0, currentPos - desiredPos);
+            }
         }
     }
 
+    private int getExcessTopPadding() {
+        if (sExcessTopPadding == -1) {
+            sExcessTopPadding = (int) (mChipHeight + mLineSpacingExtra);
+        }
+        return sExcessTopPadding;
+    }
+
+    public boolean lastCharacterIsCommitCharacter(CharSequence s) {
+        char last;
+        int end = getSelectionEnd() == 0 ? 0 : getSelectionEnd() - 1;
+        int len = length() - 1;
+        if (end != len) {
+            last = s.charAt(end);
+        } else {
+            last = s.charAt(len);
+        }
+        return last == COMMIT_CHAR_COMMA || last == COMMIT_CHAR_SEMICOLON;
+    }
+
+    public boolean isGeneratedContact(DrawableRecipientChip chip) {
+        long contactId = chip.getContactId();
+        return contactId == RecipientEntry.INVALID_CONTACT
+                || (!isPhoneQuery() && contactId == RecipientEntry.GENERATED_CONTACT);
+    }
+
     /**
      * Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}.
      */
@@ -2191,7 +2343,7 @@
     }
 
     private void handlePasteAndReplace() {
-        ArrayList<RecipientChip> created = handlePaste();
+        ArrayList<DrawableRecipientChip> created = handlePaste();
         if (created != null && created.size() > 0) {
             // Perform reverse lookups on the pasted contacts.
             IndividualReplacementTask replace = new IndividualReplacementTask();
@@ -2200,17 +2352,17 @@
     }
 
     // Visible for testing.
-    /* package */ArrayList<RecipientChip> handlePaste() {
+    /* package */ArrayList<DrawableRecipientChip> handlePaste() {
         String text = getText().toString();
         int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
         String lastAddress = text.substring(originalTokenStart);
         int tokenStart = originalTokenStart;
-        int prevTokenStart = tokenStart;
-        RecipientChip findChip = null;
-        ArrayList<RecipientChip> created = new ArrayList<RecipientChip>();
+        int prevTokenStart = 0;
+        DrawableRecipientChip findChip = null;
+        ArrayList<DrawableRecipientChip> created = new ArrayList<DrawableRecipientChip>();
         if (tokenStart != 0) {
             // There are things before this!
-            while (tokenStart != 0 && findChip == null) {
+            while (tokenStart != 0 && findChip == null && tokenStart != prevTokenStart) {
                 prevTokenStart = tokenStart;
                 tokenStart = mTokenizer.findTokenStart(text, tokenStart);
                 findChip = findChip(tokenStart);
@@ -2220,11 +2372,15 @@
                     tokenStart = prevTokenStart;
                 }
                 int tokenEnd;
-                RecipientChip createdChip;
+                DrawableRecipientChip createdChip;
                 while (tokenStart < originalTokenStart) {
-                    tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(text, tokenStart));
+                    tokenEnd = movePastTerminators(mTokenizer.findTokenEnd(getText().toString(),
+                            tokenStart));
                     commitChip(tokenStart, tokenEnd, getText());
                     createdChip = findChip(tokenStart);
+                    if (createdChip == null) {
+                        break;
+                    }
                     // +1 for the space at the end.
                     tokenStart = getSpannable().getSpanEnd(createdChip) + 1;
                     created.add(createdChip);
@@ -2260,12 +2416,13 @@
     }
 
     private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
-        private RecipientChip createFreeChip(RecipientEntry entry) {
+        private DrawableRecipientChip createFreeChip(RecipientEntry entry) {
             try {
                 if (mNoChips) {
                     return null;
                 }
-                return constructChipSpan(entry, -1, false);
+                return constructChipSpan(entry, false,
+                        false /*leave space for contact icon */);
             } catch (NullPointerException e) {
                 Log.e(TAG, e.getMessage(), e);
                 return null;
@@ -2273,6 +2430,35 @@
         }
 
         @Override
+        protected void onPreExecute() {
+            // Ensure everything is in chip-form already, so we don't have text that slowly gets
+            // replaced
+            final List<DrawableRecipientChip> originalRecipients =
+                    new ArrayList<DrawableRecipientChip>();
+            final DrawableRecipientChip[] existingChips = getSortedRecipients();
+            for (int i = 0; i < existingChips.length; i++) {
+                originalRecipients.add(existingChips[i]);
+            }
+            if (mRemovedSpans != null) {
+                originalRecipients.addAll(mRemovedSpans);
+            }
+
+            final List<DrawableRecipientChip> replacements =
+                    new ArrayList<DrawableRecipientChip>(originalRecipients.size());
+
+            for (final DrawableRecipientChip chip : originalRecipients) {
+                if (RecipientEntry.isCreatedRecipient(chip.getEntry().getContactId())
+                        && getSpannable().getSpanStart(chip) != -1) {
+                    replacements.add(createFreeChip(chip.getEntry()));
+                } else {
+                    replacements.add(null);
+                }
+            }
+
+            processReplacements(originalRecipients, replacements);
+        }
+
+        @Override
         protected Void doInBackground(Void... params) {
             if (mIndividualReplacements != null) {
                 mIndividualReplacements.cancel(true);
@@ -2280,105 +2466,190 @@
             // For each chip in the list, look up the matching contact.
             // If there is a match, replace that chip with the matching
             // chip.
-            final ArrayList<RecipientChip> originalRecipients = new ArrayList<RecipientChip>();
-            RecipientChip[] existingChips = getSortedRecipients();
+            final ArrayList<DrawableRecipientChip> recipients =
+                    new ArrayList<DrawableRecipientChip>();
+            DrawableRecipientChip[] existingChips = getSortedRecipients();
             for (int i = 0; i < existingChips.length; i++) {
-                originalRecipients.add(existingChips[i]);
+                recipients.add(existingChips[i]);
             }
             if (mRemovedSpans != null) {
-                originalRecipients.addAll(mRemovedSpans);
+                recipients.addAll(mRemovedSpans);
             }
             ArrayList<String> addresses = new ArrayList<String>();
-            RecipientChip chip;
-            for (int i = 0; i < originalRecipients.size(); i++) {
-                chip = originalRecipients.get(i);
+            DrawableRecipientChip chip;
+            for (int i = 0; i < recipients.size(); i++) {
+                chip = recipients.get(i);
                 if (chip != null) {
                     addresses.add(createAddressText(chip.getEntry()));
                 }
             }
-            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
-                    .getMatchingRecipients(getContext(), addresses);
-            final ArrayList<RecipientChip> replacements = new ArrayList<RecipientChip>();
-            for (final RecipientChip temp : originalRecipients) {
-                RecipientEntry entry = null;
-                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
-                        && getSpannable().getSpanStart(temp) != -1) {
-                    // Replace this.
-                    entry = createValidatedEntry(entries.get(tokenizeAddress(temp.getEntry()
-                            .getDestination())));
-                }
-                if (entry != null) {
-                    replacements.add(createFreeChip(entry));
-                } else {
-                    replacements.add(temp);
-                }
-            }
+            RecipientAlternatesAdapter.getMatchingRecipients(getContext(), addresses,
+                    ((BaseRecipientAdapter) getAdapter()).getAccount(),
+                    new RecipientMatchCallback() {
+
+                        @Override
+                        public void matchesFound(Map<String, RecipientEntry> entries) {
+                            final ArrayList<DrawableRecipientChip> replacements =
+                                    new ArrayList<DrawableRecipientChip>();
+                            for (final DrawableRecipientChip temp : recipients) {
+                                RecipientEntry entry = null;
+                                if (temp != null && RecipientEntry.isCreatedRecipient(
+                                        temp.getEntry().getContactId())
+                                        && getSpannable().getSpanStart(temp) != -1) {
+                                    // Replace this.
+                                    entry = createValidatedEntry(
+                                            entries.get(tokenizeAddress(temp.getEntry()
+                                                    .getDestination())));
+                                }
+                                if (entry != null) {
+                                    replacements.add(createFreeChip(entry));
+                                } else {
+                                    replacements.add(null);
+                                }
+                            }
+                            processReplacements(recipients, replacements);
+                        }
+
+                        @Override
+                        public void matchesNotFound(final Set<String> unfoundAddresses) {
+                            final List<DrawableRecipientChip> replacements =
+                                    new ArrayList<DrawableRecipientChip>(unfoundAddresses.size());
+
+                            for (final DrawableRecipientChip temp : recipients) {
+                                if (temp != null && RecipientEntry.isCreatedRecipient(
+                                        temp.getEntry().getContactId())
+                                        && getSpannable().getSpanStart(temp) != -1) {
+                                    if (unfoundAddresses.contains(
+                                            temp.getEntry().getDestination())) {
+                                        replacements.add(createFreeChip(temp.getEntry()));
+                                    } else {
+                                        replacements.add(null);
+                                    }
+                                } else {
+                                    replacements.add(null);
+                                }
+                            }
+
+                            processReplacements(recipients, replacements);
+                        }
+                    });
+            return null;
+        }
+
+        private void processReplacements(final List<DrawableRecipientChip> recipients,
+                final List<DrawableRecipientChip> replacements) {
             if (replacements != null && replacements.size() > 0) {
-                mHandler.post(new Runnable() {
+                final Runnable runnable = new Runnable() {
                     @Override
                     public void run() {
-                        SpannableStringBuilder text = new SpannableStringBuilder(getText()
-                                .toString());
                         Editable oldText = getText();
                         int start, end;
                         int i = 0;
-                        for (RecipientChip chip : originalRecipients) {
-                            start = oldText.getSpanStart(chip);
-                            if (start != -1) {
-                                end = oldText.getSpanEnd(chip);
-                                oldText.removeSpan(chip);
-                                // Leave a spot for the space!
-                                RecipientChip replacement = replacements.get(i);
-                                text.setSpan(replacement, start, end,
-                                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-                                replacement.setOriginalText(text.toString().substring(start, end));
+                        for (DrawableRecipientChip chip : recipients) {
+                            DrawableRecipientChip replacement = replacements.get(i);
+                            if (replacement != null) {
+                                final RecipientEntry oldEntry = chip.getEntry();
+                                final RecipientEntry newEntry = replacement.getEntry();
+                                final boolean isBetter =
+                                        RecipientAlternatesAdapter.getBetterRecipient(
+                                                oldEntry, newEntry) == newEntry;
+
+                                if (isBetter) {
+                                    // Find the location of the chip in the text currently shown.
+                                    start = oldText.getSpanStart(chip);
+                                    if (start != -1) {
+                                        // Replacing the entirety of what the chip represented,
+                                        // including the extra space dividing it from other chips.
+                                        end = oldText.getSpanEnd(chip) + 1;
+                                        oldText.removeSpan(chip);
+                                        // Make sure we always have just 1 space at the end to
+                                        // separate this chip from the next chip.
+                                        SpannableString displayText =
+                                                new SpannableString(createAddressText(
+                                                        replacement.getEntry()).trim()
+                                                        + " ");
+                                        displayText.setSpan(replacement, 0,
+                                                displayText.length() - 1,
+                                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                                        // Replace the old text we found with with the new display
+                                        // text, which now may also contain the display name of the
+                                        // recipient.
+                                        oldText.replace(start, end, displayText);
+                                        replacement.setOriginalText(displayText.toString());
+                                        replacements.set(i, null);
+
+                                        recipients.set(i, replacement);
+                                    }
+                                }
                             }
                             i++;
                         }
-                        originalRecipients.clear();
-                        setText(text);
                     }
-                });
+                };
+
+                if (Looper.myLooper() == Looper.getMainLooper()) {
+                    runnable.run();
+                } else {
+                    mHandler.post(runnable);
+                }
             }
-            return null;
         }
     }
 
-    private class IndividualReplacementTask extends AsyncTask<Object, Void, Void> {
-        @SuppressWarnings("unchecked")
+    private class IndividualReplacementTask
+            extends AsyncTask<ArrayList<DrawableRecipientChip>, Void, Void> {
         @Override
-        protected Void doInBackground(Object... params) {
+        protected Void doInBackground(ArrayList<DrawableRecipientChip>... params) {
             // For each chip in the list, look up the matching contact.
             // If there is a match, replace that chip with the matching
             // chip.
-            final ArrayList<RecipientChip> originalRecipients =
-                (ArrayList<RecipientChip>) params[0];
+            final ArrayList<DrawableRecipientChip> originalRecipients = params[0];
             ArrayList<String> addresses = new ArrayList<String>();
-            RecipientChip chip;
+            DrawableRecipientChip chip;
             for (int i = 0; i < originalRecipients.size(); i++) {
                 chip = originalRecipients.get(i);
                 if (chip != null) {
                     addresses.add(createAddressText(chip.getEntry()));
                 }
             }
-            HashMap<String, RecipientEntry> entries = RecipientAlternatesAdapter
-                    .getMatchingRecipients(getContext(), addresses);
-            for (final RecipientChip temp : originalRecipients) {
-                if (RecipientEntry.isCreatedRecipient(temp.getEntry().getContactId())
-                        && getSpannable().getSpanStart(temp) != -1) {
-                    // Replace this.
-                    final RecipientEntry entry = createValidatedEntry(entries
-                            .get(tokenizeAddress(temp.getEntry().getDestination()).toLowerCase()));
-                    if (entry != null) {
-                        mHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                replaceChip(temp, entry);
+            RecipientAlternatesAdapter.getMatchingRecipients(getContext(), addresses,
+                    ((BaseRecipientAdapter) getAdapter()).getAccount(),
+                    new RecipientMatchCallback() {
+
+                        @Override
+                        public void matchesFound(Map<String, RecipientEntry> entries) {
+                            for (final DrawableRecipientChip temp : originalRecipients) {
+                                if (RecipientEntry.isCreatedRecipient(temp.getEntry()
+                                        .getContactId())
+                                        && getSpannable().getSpanStart(temp) != -1) {
+                                    // Replace this.
+                                    RecipientEntry entry = createValidatedEntry(entries
+                                            .get(tokenizeAddress(temp.getEntry().getDestination())
+                                                    .toLowerCase()));
+                                    // If we don't have a validated contact
+                                    // match, just use the
+                                    // entry as it existed before.
+                                    if (entry == null && !isPhoneQuery()) {
+                                        entry = temp.getEntry();
+                                    }
+                                    final RecipientEntry tempEntry = entry;
+                                    if (tempEntry != null) {
+                                        mHandler.post(new Runnable() {
+                                            @Override
+                                            public void run() {
+                                                replaceChip(temp, tempEntry);
+                                            }
+                                        });
+                                    }
+                                }
                             }
-                        });
-                    }
-                }
-            }
+                        }
+
+                        @Override
+                        public void matchesNotFound(final Set<String> unfoundAddresses) {
+                            // No action required
+                        }
+                    });
             return null;
         }
     }
@@ -2413,7 +2684,7 @@
         float x = event.getX();
         float y = event.getY();
         int offset = putOffsetInRange(getOffsetForPosition(x, y));
-        RecipientChip currentChip = findChip(offset);
+        DrawableRecipientChip currentChip = findChip(offset);
         if (currentChip != null) {
             if (mDragEnabled) {
                 // Start drag-and-drop for the selected chip.
@@ -2435,7 +2706,7 @@
     /**
      * Starts drag-and-drop for the selected chip.
      */
-    private void startDrag(RecipientChip currentChip) {
+    private void startDrag(DrawableRecipientChip currentChip) {
         String address = currentChip.getEntry().getDestination();
         ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA);
 
@@ -2470,22 +2741,22 @@
      * Drag shadow for a {@link RecipientChip}.
      */
     private final class RecipientChipShadow extends DragShadowBuilder {
-        private final RecipientChip mChip;
+        private final DrawableRecipientChip mChip;
 
-        public RecipientChipShadow(RecipientChip chip) {
+        public RecipientChipShadow(DrawableRecipientChip chip) {
             mChip = chip;
         }
 
         @Override
         public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
-            Rect rect = mChip.getDrawable().getBounds();
+            Rect rect = mChip.getBounds();
             shadowSize.set(rect.width(), rect.height());
             shadowTouchPoint.set(rect.centerX(), rect.centerY());
         }
 
         @Override
         public void onDrawShadow(Canvas canvas) {
-            mChip.getDrawable().draw(canvas);
+            mChip.draw(canvas);
         }
     }
 
@@ -2541,7 +2812,8 @@
     }
 
     protected boolean isPhoneQuery() {
-        return ((BaseRecipientAdapter)getAdapter()).getQueryType() ==
-                BaseRecipientAdapter.QUERY_TYPE_PHONE;
+        return getAdapter() != null
+                && ((BaseRecipientAdapter) getAdapter()).getQueryType()
+                    == BaseRecipientAdapter.QUERY_TYPE_PHONE;
     }
 }
diff --git a/chips/src/com/android/ex/chips/RecipientEntry.java b/chips/src/com/android/ex/chips/RecipientEntry.java
index 0448229..44bc767 100644
--- a/chips/src/com/android/ex/chips/RecipientEntry.java
+++ b/chips/src/com/android/ex/chips/RecipientEntry.java
@@ -19,6 +19,8 @@
 import android.net.Uri;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.DisplayNameSources;
+import android.text.util.Rfc822Token;
+import android.text.util.Rfc822Tokenizer;
 
 /**
  * Represents one entry inside recipient auto-complete list.
@@ -65,29 +67,16 @@
 
     private final Uri mPhotoThumbnailUri;
 
+    private boolean mIsValid;
     /**
      * This can be updated after this object being constructed, when the photo is fetched
      * from remote directories.
      */
     private byte[] mPhotoBytes;
 
-    private RecipientEntry(int entryType) {
-        mEntryType = entryType;
-        mDisplayName = null;
-        mDestination = null;
-        mDestinationType = INVALID_DESTINATION_TYPE;
-        mDestinationLabel = null;
-        mContactId = -1;
-        mDataId = -1;
-        mPhotoThumbnailUri = null;
-        mPhotoBytes = null;
-        mIsDivider = true;
-    }
-
-    private RecipientEntry(
-            int entryType, String displayName,
-            String destination, int destinationType, String destinationLabel,
-            long contactId, long dataId, Uri photoThumbnailUri, boolean isFirstLevel) {
+    private RecipientEntry(int entryType, String displayName, String destination,
+            int destinationType, String destinationLabel, long contactId, long dataId,
+            Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid) {
         mEntryType = entryType;
         mIsFirstLevel = isFirstLevel;
         mDisplayName = displayName;
@@ -99,6 +88,11 @@
         mPhotoThumbnailUri = photoThumbnailUri;
         mPhotoBytes = null;
         mIsDivider = false;
+        mIsValid = isValid;
+    }
+
+    public boolean isValid() {
+        return mIsValid;
     }
 
     /**
@@ -114,10 +108,23 @@
      * This address has not been resolved to a contact and therefore does not
      * have a contact id or photo.
      */
-    public static RecipientEntry constructFakeEntry(String address) {
-        return new RecipientEntry(ENTRY_TYPE_PERSON, address, address,
+    public static RecipientEntry constructFakeEntry(final String address, final boolean isValid) {
+        final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
+        final String tokenizedAddress = tokens.length > 0 ? tokens[0].getAddress() : address;
+
+        return new RecipientEntry(ENTRY_TYPE_PERSON, tokenizedAddress, tokenizedAddress,
                 INVALID_DESTINATION_TYPE, null,
-                INVALID_CONTACT, INVALID_CONTACT, null, true);
+                INVALID_CONTACT, INVALID_CONTACT, null, true, isValid);
+    }
+
+    /**
+     * Construct a RecipientEntry from just a phone number.
+     */
+    public static RecipientEntry constructFakePhoneEntry(final String phoneNumber,
+            final boolean isValid) {
+        return new RecipientEntry(ENTRY_TYPE_PERSON, phoneNumber, phoneNumber,
+                INVALID_DESTINATION_TYPE, null,
+                INVALID_CONTACT, INVALID_CONTACT, null, true, isValid);
     }
 
     /**
@@ -136,41 +143,37 @@
      * with both an associated display name. This address has not been resolved
      * to a contact and therefore does not have a contact id or photo.
      */
-    public static RecipientEntry constructGeneratedEntry(String display, String address) {
-        return new RecipientEntry(ENTRY_TYPE_PERSON, display,
-                address, INVALID_DESTINATION_TYPE, null,
-                GENERATED_CONTACT, GENERATED_CONTACT, null, true);
+    public static RecipientEntry constructGeneratedEntry(String display, String address,
+            boolean isValid) {
+        return new RecipientEntry(ENTRY_TYPE_PERSON, display, address, INVALID_DESTINATION_TYPE,
+                null, GENERATED_CONTACT, GENERATED_CONTACT, null, true, isValid);
     }
 
-    public static RecipientEntry constructTopLevelEntry(
-            String displayName, int displayNameSource, String destination, int destinationType,
-            String destinationLabel, long contactId, long dataId, Uri photoThumbnailUri) {
-        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource, displayName,
-                    destination),
-                destination, destinationType, destinationLabel,
-                contactId, dataId,
-                photoThumbnailUri, true);
+    public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
+            String destination, int destinationType, String destinationLabel, long contactId,
+            long dataId, Uri photoThumbnailUri, boolean isValid) {
+        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
+                displayName, destination), destination, destinationType, destinationLabel,
+                contactId, dataId, photoThumbnailUri, true, isValid);
     }
 
-    public static RecipientEntry constructTopLevelEntry(
-            String displayName, int displayNameSource, String destination, int destinationType,
-            String destinationLabel, long contactId, long dataId,
-            String thumbnailUriAsString) {
-        return new RecipientEntry(
-                ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource, displayName, destination),
-                destination, destinationType, destinationLabel,
-                contactId, dataId,
-                (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString) : null), true);
+    public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
+            String destination, int destinationType, String destinationLabel, long contactId,
+            long dataId, String thumbnailUriAsString, boolean isValid) {
+        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
+                displayName, destination), destination, destinationType, destinationLabel,
+                contactId, dataId, (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString)
+                        : null), true, isValid);
     }
 
-    public static RecipientEntry constructSecondLevelEntry(
-            String displayName, int displayNameSource, String destination, int destinationType,
-            String destinationLabel, long contactId, long dataId, String thumbnailUriAsString) {
-        return new RecipientEntry(
-                ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource, displayName, destination),
-                destination, destinationType, destinationLabel,
-                contactId, dataId,
-                (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString) : null), false);
+    public static RecipientEntry constructSecondLevelEntry(String displayName,
+            int displayNameSource, String destination, int destinationType,
+            String destinationLabel, long contactId, long dataId, String thumbnailUriAsString,
+            boolean isValid) {
+        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
+                displayName, destination), destination, destinationType, destinationLabel,
+                contactId, dataId, (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString)
+                        : null), false, isValid);
     }
 
     public int getEntryType() {
@@ -226,4 +229,9 @@
     public boolean isSelectable() {
         return mEntryType == ENTRY_TYPE_PERSON;
     }
+
+    @Override
+    public String toString() {
+        return mDisplayName + " <" + mDestination + ">, isValid=" + mIsValid;
+    }
 }
\ No newline at end of file
diff --git a/chips/src/com/android/ex/chips/SingleRecipientArrayAdapter.java b/chips/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
index 8131fc3..0571a4e 100644
--- a/chips/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
+++ b/chips/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
@@ -43,7 +43,7 @@
         if (convertView == null) {
             convertView = newView();
         }
-        bindView(convertView, convertView.getContext(), getItem(position));
+        bindView(convertView, getItem(position));
         return convertView;
     }
 
@@ -51,7 +51,7 @@
         return mLayoutInflater.inflate(mLayoutId, null);
     }
 
-    private void bindView(View view, Context context, RecipientEntry entry) {
+    private static void bindView(View view, RecipientEntry entry) {
         TextView display = (TextView) view.findViewById(android.R.id.title);
         ImageView imageView = (ImageView) view.findViewById(android.R.id.icon);
         display.setText(entry.getDisplayName());
diff --git a/chips/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java b/chips/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java
new file mode 100644
index 0000000..a080ee7
--- /dev/null
+++ b/chips/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2013 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.ex.chips.recipientchip;
+
+import com.android.ex.chips.RecipientEntry;
+
+/**
+ * BaseRecipientChip defines an object that contains information relevant to a
+ * particular recipient.
+ */
+interface BaseRecipientChip {
+
+    /**
+     * Set the selected state of the chip.
+     */
+    void setSelected(boolean selected);
+
+    /**
+     * Return true if the chip is selected.
+     */
+    boolean isSelected();
+
+    /**
+     * Get the text displayed in the chip.
+     */
+    CharSequence getDisplay();
+
+    /**
+     * Get the text value this chip represents.
+     */
+    CharSequence getValue();
+
+    /**
+     * Get the id of the contact associated with this chip.
+     */
+    long getContactId();
+
+    /**
+     * Get the id of the data associated with this chip.
+     */
+    long getDataId();
+
+    /**
+     * Get associated RecipientEntry.
+     */
+    RecipientEntry getEntry();
+
+    /**
+     * Set the text in the edittextview originally associated with this chip
+     * before any reverse lookups.
+     */
+    void setOriginalText(String text);
+
+    /**
+     * Set the text in the edittextview originally associated with this chip
+     * before any reverse lookups.
+     */
+    CharSequence getOriginalText();
+}
diff --git a/chips/src/com/android/ex/chips/recipientchip/DrawableRecipientChip.java b/chips/src/com/android/ex/chips/recipientchip/DrawableRecipientChip.java
new file mode 100644
index 0000000..396a8ac
--- /dev/null
+++ b/chips/src/com/android/ex/chips/recipientchip/DrawableRecipientChip.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2011 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.ex.chips.recipientchip;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+
+/**
+ * RecipientChip defines a drawable object that contains information relevant to a
+ * particular recipient.
+ */
+public interface DrawableRecipientChip extends BaseRecipientChip {
+    /**
+     * Get the bounds of the chip; may be 0,0 if it is not visibly rendered.
+     */
+    Rect getBounds();
+
+    /**
+     * Draw the chip.
+     */
+    void draw(Canvas canvas);
+}
diff --git a/chips/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java b/chips/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java
new file mode 100644
index 0000000..0380a81
--- /dev/null
+++ b/chips/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2011 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.ex.chips.recipientchip;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.text.style.ReplacementSpan;
+
+import com.android.ex.chips.RecipientEntry;
+
+/**
+ * RecipientChip defines a span that contains information relevant to a
+ * particular recipient.
+ */
+public class InvisibleRecipientChip extends ReplacementSpan implements DrawableRecipientChip {
+    private final SimpleRecipientChip mDelegate;
+
+    public InvisibleRecipientChip(final RecipientEntry entry) {
+        super();
+
+        mDelegate = new SimpleRecipientChip(entry);
+    }
+
+    @Override
+    public void setSelected(final boolean selected) {
+        mDelegate.setSelected(selected);
+    }
+
+    @Override
+    public boolean isSelected() {
+        return mDelegate.isSelected();
+    }
+
+    @Override
+    public CharSequence getDisplay() {
+        return mDelegate.getDisplay();
+    }
+
+    @Override
+    public CharSequence getValue() {
+        return mDelegate.getValue();
+    }
+
+    @Override
+    public long getContactId() {
+        return mDelegate.getContactId();
+    }
+
+    @Override
+    public long getDataId() {
+        return mDelegate.getDataId();
+    }
+
+    @Override
+    public RecipientEntry getEntry() {
+        return mDelegate.getEntry();
+    }
+
+    @Override
+    public void setOriginalText(final String text) {
+        mDelegate.setOriginalText(text);
+    }
+
+    @Override
+    public CharSequence getOriginalText() {
+        return mDelegate.getOriginalText();
+    }
+
+    @Override
+    public void draw(final Canvas canvas, final CharSequence text, final int start, final int end,
+            final float x, final int top, final int y, final int bottom, final Paint paint) {
+        // Do nothing.
+    }
+
+    @Override
+    public int getSize(final Paint paint, final CharSequence text, final int start, final int end,
+            final Paint.FontMetricsInt fm) {
+        return 0;
+    }
+
+    @Override
+    public Rect getBounds() {
+        return new Rect(0, 0, 0, 0);
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        // do nothing.
+    }
+}
diff --git a/chips/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java b/chips/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java
new file mode 100644
index 0000000..c04b3be
--- /dev/null
+++ b/chips/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2013 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.ex.chips.recipientchip;
+
+import com.android.ex.chips.RecipientEntry;
+
+import android.text.TextUtils;
+
+class SimpleRecipientChip implements BaseRecipientChip {
+    private final CharSequence mDisplay;
+
+    private final CharSequence mValue;
+
+    private final long mContactId;
+
+    private final long mDataId;
+
+    private final RecipientEntry mEntry;
+
+    private boolean mSelected = false;
+
+    private CharSequence mOriginalText;
+
+    public SimpleRecipientChip(final RecipientEntry entry) {
+        mDisplay = entry.getDisplayName();
+        mValue = entry.getDestination().trim();
+        mContactId = entry.getContactId();
+        mDataId = entry.getDataId();
+        mEntry = entry;
+    }
+
+    @Override
+    public void setSelected(final boolean selected) {
+        mSelected = selected;
+    }
+
+    @Override
+    public boolean isSelected() {
+        return mSelected;
+    }
+
+    @Override
+    public CharSequence getDisplay() {
+        return mDisplay;
+    }
+
+    @Override
+    public CharSequence getValue() {
+        return mValue;
+    }
+
+    @Override
+    public long getContactId() {
+        return mContactId;
+    }
+
+    @Override
+    public long getDataId() {
+        return mDataId;
+    }
+
+    @Override
+    public RecipientEntry getEntry() {
+        return mEntry;
+    }
+
+    @Override
+    public void setOriginalText(final String text) {
+        if (TextUtils.isEmpty(text)) {
+            mOriginalText = text;
+        } else {
+            mOriginalText = text.trim();
+        }
+    }
+
+    @Override
+    public CharSequence getOriginalText() {
+        return !TextUtils.isEmpty(mOriginalText) ? mOriginalText : mEntry.getDestination();
+    }
+
+    @Override
+    public String toString() {
+        return mDisplay + " <" + mValue + ">";
+    }
+}
\ No newline at end of file
diff --git a/chips/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java b/chips/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java
new file mode 100644
index 0000000..acade7f
--- /dev/null
+++ b/chips/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2011 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.ex.chips.recipientchip;
+
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.style.DynamicDrawableSpan;
+import android.text.style.ImageSpan;
+
+import com.android.ex.chips.RecipientEntry;
+
+/**
+ * VisibleRecipientChip defines an ImageSpan that contains information relevant to a
+ * particular recipient and renders a background asset to go with it.
+ */
+public class VisibleRecipientChip extends ImageSpan implements DrawableRecipientChip {
+    private final SimpleRecipientChip mDelegate;
+
+    public VisibleRecipientChip(final Drawable drawable, final RecipientEntry entry) {
+        super(drawable, DynamicDrawableSpan.ALIGN_BOTTOM);
+
+        mDelegate = new SimpleRecipientChip(entry);
+    }
+
+    @Override
+    public void setSelected(final boolean selected) {
+        mDelegate.setSelected(selected);
+    }
+
+    @Override
+    public boolean isSelected() {
+        return mDelegate.isSelected();
+    }
+
+    @Override
+    public CharSequence getDisplay() {
+        return mDelegate.getDisplay();
+    }
+
+    @Override
+    public CharSequence getValue() {
+        return mDelegate.getValue();
+    }
+
+    @Override
+    public long getContactId() {
+        return mDelegate.getContactId();
+    }
+
+    @Override
+    public long getDataId() {
+        return mDelegate.getDataId();
+    }
+
+    @Override
+    public RecipientEntry getEntry() {
+        return mDelegate.getEntry();
+    }
+
+    @Override
+    public void setOriginalText(final String text) {
+        mDelegate.setOriginalText(text);
+    }
+
+    @Override
+    public CharSequence getOriginalText() {
+        return mDelegate.getOriginalText();
+    }
+
+    @Override
+    public Rect getBounds() {
+        return getDrawable().getBounds();
+    }
+
+    @Override
+    public void draw(final Canvas canvas) {
+        getDrawable().draw(canvas);
+    }
+
+    @Override
+    public String toString() {
+        return mDelegate.toString();
+    }
+}
diff --git a/chips/tests/Android.mk b/chips/tests/Android.mk
index 313af7d..b01aa0c 100644
--- a/chips/tests/Android.mk
+++ b/chips/tests/Android.mk
@@ -20,7 +20,6 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_SDK_VERSION := current
 LOCAL_CERTIFICATE := platform
-LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_STATIC_JAVA_LIBRARIES += android-common-chips
 LOCAL_RESOURCE_DIR := frameworks/ex/chips/res/
 LOCAL_AAPT_FLAGS := --auto-add-overlay
diff --git a/chips/tests/src/com/android/ex/chips/ChipsTest.java b/chips/tests/src/com/android/ex/chips/ChipsTest.java
index 6639bcb..7963086 100644
--- a/chips/tests/src/com/android/ex/chips/ChipsTest.java
+++ b/chips/tests/src/com/android/ex/chips/ChipsTest.java
@@ -28,12 +28,17 @@
 import android.text.util.Rfc822Tokenizer;
 import android.widget.TextView;
 
+import com.android.ex.chips.BaseRecipientAdapter;
 import com.android.ex.chips.RecipientEditTextView;
 import com.android.ex.chips.RecipientEntry;
+import com.android.ex.chips.recipientchip.DrawableRecipientChip;
+import com.android.ex.chips.recipientchip.VisibleRecipientChip;;
+
+import java.util.regex.Pattern;
 
 @SmallTest
 public class ChipsTest extends AndroidTestCase {
-    private RecipientChip[] mMockRecips;
+    private DrawableRecipientChip[] mMockRecips;
 
     private RecipientEntry[] mMockEntries;
 
@@ -50,7 +55,7 @@
         }
 
         @Override
-        public RecipientChip[] getSortedRecipients() {
+        public DrawableRecipientChip[] getSortedRecipients() {
             return mMockRecips;
         }
 
@@ -79,7 +84,7 @@
         }
 
         @Override
-        public RecipientChip[] getSortedRecipients() {
+        public DrawableRecipientChip[] getSortedRecipients() {
             return mMockRecips;
         }
 
@@ -120,9 +125,14 @@
     }
 
     private class TestBaseRecipientAdapter extends BaseRecipientAdapter {
-        public TestBaseRecipientAdapter(Context context) {
+        public TestBaseRecipientAdapter(final Context context) {
             super(context);
         }
+
+        public TestBaseRecipientAdapter(final Context context, final int preferredMaxResultCount,
+                final int queryMode) {
+            super(context, preferredMaxResultCount, queryMode);
+        }
     }
 
     private MockRecipientEditTextView createViewForTesting() {
@@ -135,15 +145,15 @@
     public void testCreateDisplayText() {
         RecipientEditTextView view = createViewForTesting();
         RecipientEntry entry = RecipientEntry.constructGeneratedEntry("User Name, Jr",
-                "user@username.com");
+                "user@username.com", true);
         String testAddress = view.createAddressText(entry);
         String testDisplay = view.createChipDisplayText(entry);
         assertEquals("Expected a properly formatted RFC email address",
                 "\"User Name, Jr\" <user@username.com>, ", testAddress);
         assertEquals("Expected a displayable name", "User Name, Jr", testDisplay);
 
-
-        RecipientEntry alreadyFormatted = RecipientEntry.constructFakeEntry("user@username.com, ");
+        RecipientEntry alreadyFormatted =
+                RecipientEntry.constructFakeEntry("user@username.com, ", true);
         testAddress = view.createAddressText(alreadyFormatted);
         testDisplay = view.createChipDisplayText(alreadyFormatted);
         assertEquals("Expected a properly formatted RFC email address", "<user@username.com>, ",
@@ -151,13 +161,13 @@
         assertEquals("Expected a displayable name", "user@username.com", testDisplay);
 
         RecipientEntry alreadyFormattedNoSpace = RecipientEntry
-                .constructFakeEntry("user@username.com,");
+                .constructFakeEntry("user@username.com,", true);
         testAddress = view.createAddressText(alreadyFormattedNoSpace);
         assertEquals("Expected a properly formatted RFC email address", "<user@username.com>, ",
                 testAddress);
 
         RecipientEntry alreadyNamed = RecipientEntry.constructGeneratedEntry("User Name",
-                "\"User Name, Jr\" <user@username.com>");
+                "\"User Name, Jr\" <user@username.com>", true);
         testAddress = view.createAddressText(alreadyNamed);
         testDisplay = view.createChipDisplayText(alreadyNamed);
         assertEquals(
@@ -254,8 +264,8 @@
         String first = (String) mTokenizer.terminateToken("FIRST");
         String second = (String) mTokenizer.terminateToken("SECOND");
         String third = (String) mTokenizer.terminateToken("THIRD");
-        String fourth = (String) ("FOURTH,");
-        String fifth = (String) ("FIFTH,");
+        String fourth = "FOURTH,";
+        String fifth = "FIFTH,";
         mEditable = new SpannableStringBuilder();
         mEditable.append(first+second+third+fourth+fifth);
         assertEquals(view.countTokens(mEditable), 5);
@@ -620,7 +630,7 @@
         mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
         assertEquals(mEditable.toString(), first + second + third);
         view.replaceChip(mMockRecips[mMockRecips.length - 3], RecipientEntry
-                .constructGeneratedEntry("replacement", "replacement@replacement.com"));
+                .constructGeneratedEntry("replacement", "replacement@replacement.com", true));
         assertEquals(mEditable.toString(), mTokenizer
                 .terminateToken("replacement <replacement@replacement.com>")
                 + second + third);
@@ -636,10 +646,11 @@
         assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), mEditable
                 .toString().indexOf(third)
                 + third.trim().length());
-        RecipientChip[] spans = mEditable.getSpans(0, mEditable.length(), RecipientChip.class);
+        DrawableRecipientChip[] spans =
+                mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class);
         assertEquals(spans.length, 3);
         spans = mEditable
-                .getSpans(0, mEditable.toString().indexOf(second) - 1, RecipientChip.class);
+                .getSpans(0, mEditable.toString().indexOf(second) - 1, DrawableRecipientChip.class);
         assertEquals((String) spans[0].getDisplay(), "replacement");
 
 
@@ -657,7 +668,7 @@
         mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
         assertEquals(mEditable.toString(), first + second + third);
         view.replaceChip(mMockRecips[mMockRecips.length - 2], RecipientEntry
-                .constructGeneratedEntry("replacement", "replacement@replacement.com"));
+                .constructGeneratedEntry("replacement", "replacement@replacement.com", true));
         assertEquals(mEditable.toString(), first + mTokenizer
                 .terminateToken("replacement <replacement@replacement.com>") + third);
         assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
@@ -669,10 +680,10 @@
         assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), mEditable
                 .toString().indexOf(third)
                 + third.trim().length());
-        spans = mEditable.getSpans(0, mEditable.length(), RecipientChip.class);
+        spans = mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class);
         assertEquals(spans.length, 3);
-        spans = mEditable
-                .getSpans(firstEnd, mEditable.toString().indexOf(third) - 1, RecipientChip.class);
+        spans = mEditable.getSpans(firstEnd, mEditable.toString().indexOf(third) - 1,
+                DrawableRecipientChip.class);
         assertEquals((String) spans[0].getDisplay(), "replacement");
 
 
@@ -690,7 +701,7 @@
         mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
         assertEquals(mEditable.toString(), first + second + third);
         view.replaceChip(mMockRecips[mMockRecips.length - 1], RecipientEntry
-                .constructGeneratedEntry("replacement", "replacement@replacement.com"));
+                .constructGeneratedEntry("replacement", "replacement@replacement.com", true));
         assertEquals(mEditable.toString(), first + second + mTokenizer
                 .terminateToken("replacement <replacement@replacement.com>"));
         assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
@@ -699,10 +710,10 @@
         assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), secondEnd);
         assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), -1);
         assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), -1);
-        spans = mEditable.getSpans(0, mEditable.length(), RecipientChip.class);
+        spans = mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class);
         assertEquals(spans.length, 3);
         spans = mEditable
-                .getSpans(secondEnd, mEditable.length(), RecipientChip.class);
+                .getSpans(secondEnd, mEditable.length(), DrawableRecipientChip.class);
         assertEquals((String) spans[0].getDisplay(), "replacement");
     }
 
@@ -717,7 +728,7 @@
         mEditable.append("user@user.com");
         view.setSelection(mEditable.length());
         view.handlePaste();
-        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length, 0);
+        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 0);
         assertEquals(mEditable.toString(), "user@user.com");
 
         // Test adding a single address to an empty chips field with a space at
@@ -727,7 +738,7 @@
         mEditable.append(tokenizedUser);
         view.setSelection(mEditable.length());
         view.handlePaste();
-        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length, 0);
+        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 0);
         assertEquals(mEditable.toString(), tokenizedUser);
 
         // Test adding a single address to an empty chips field with a semicolon at
@@ -737,7 +748,7 @@
         mEditable.append(tokenizedUser);
         view.setSelection(mEditable.length());
         view.handlePaste();
-        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length, 1);
+        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 1);
 
         // Test adding 2 address to an empty chips field. The second to last
         // address should become a chip and the last address should stay as
@@ -746,9 +757,9 @@
         mEditable.append("user1,user2@user.com");
         view.setSelection(mEditable.length());
         view.handlePaste();
-        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length, 1);
+        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 1);
         assertEquals(mEditable.getSpans(0, mEditable.toString().indexOf("user2@user.com"),
-                RecipientChip.class).length, 1);
+                DrawableRecipientChip.class).length, 1);
         assertEquals(mEditable.toString(), "<user1>, user2@user.com");
 
         // Test adding a single address to the end of existing chips. The existing
@@ -773,7 +784,7 @@
         mEditable.append("user@user.com");
         view.setSelection(mEditable.length());
         view.handlePaste();
-        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length,
+        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length,
                 mMockRecips.length);
         assertEquals(mEditable.toString(), first + second + third + "user@user.com");
 
@@ -790,12 +801,12 @@
         mEditable.append("user1, user2@user.com");
         view.setSelection(mEditable.length());
         view.handlePaste();
-        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length,
+        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length,
                 mMockRecips.length + 1);
         assertEquals(mEditable.getSpans(mEditable.toString().indexOf("<user1>"), mEditable
-                .toString().indexOf("user2@user.com") - 1, RecipientChip.class).length, 1);
+                .toString().indexOf("user2@user.com") - 1, DrawableRecipientChip.class).length, 1);
         assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
-                .length(), RecipientChip.class).length, 0);
+                .length(), DrawableRecipientChip.class).length, 0);
         assertEquals(mEditable.toString(), first + second + third + "<user1>, user2@user.com");
 
         // Paste 2 addresses after existing chips. We expect the first address to be turned into
@@ -812,12 +823,12 @@
         mEditable.append("user1,user2@user.com");
         view.setSelection(mEditable.length());
         view.handlePaste();
-        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length,
+        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length,
                 mMockRecips.length + 1);
         assertEquals(mEditable.getSpans(mEditable.toString().indexOf("<user1>"), mEditable
-                .toString().indexOf("user2@user.com") - 1, RecipientChip.class).length, 1);
+                .toString().indexOf("user2@user.com") - 1, DrawableRecipientChip.class).length, 1);
         assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
-                .length(), RecipientChip.class).length, 0);
+                .length(), DrawableRecipientChip.class).length, 0);
         assertEquals(mEditable.toString(), first + second + third + "<user1>, user2@user.com");
 
         // Test a complete token pasted in at the end. It should be turned into a chip.
@@ -825,11 +836,11 @@
         mEditable.append("user1, user2@user.com,");
         view.setSelection(mEditable.length());
         view.handlePaste();
-        assertEquals(mEditable.getSpans(0, mEditable.length(), RecipientChip.class).length, 2);
+        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 2);
         assertEquals(mEditable.getSpans(mEditable.toString().indexOf("<user1>"), mEditable
-                .toString().indexOf("user2@user.com") - 1, RecipientChip.class).length, 1);
+                .toString().indexOf("user2@user.com") - 1, DrawableRecipientChip.class).length, 1);
         assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
-                .length(), RecipientChip.class).length, 1);
+                .length(), DrawableRecipientChip.class).length, 1);
         assertEquals(mEditable.toString(), "<user1>, <user2@user.com>, ");
     }
 
@@ -922,11 +933,96 @@
         mMockEntries = new RecipientEntry[size];
         for (int i = 0; i < size; i++) {
             mMockEntries[i] = RecipientEntry.constructGeneratedEntry("user",
-                    "user@username.com");
+                    "user@username.com", true);
         }
-        mMockRecips = new RecipientChip[size];
+        mMockRecips = new DrawableRecipientChip[size];
         for (int i = 0; i < size; i++) {
-            mMockRecips[i] = new RecipientChip(null, mMockEntries[i], i);
+            mMockRecips[i] = new VisibleRecipientChip(null, mMockEntries[i]);
         }
     }
+
+    /**
+     * <p>
+     * Ensure the original text is always accurate, regardless of the type of email. The original
+     * text is used to determine where to display the chip span. If this test fails, it means some
+     * text that should be turned into one whole chip may behave unexpectedly.
+     * </p>
+     * <p>
+     * For example, a bug was seen where
+     *
+     * <pre>
+     * "Android User" <android@example.com>
+     * </pre>
+     *
+     * was converted to
+     *
+     * <pre>
+     * Android User [android@example.com]
+     * </pre>
+     *
+     * where text inside [] is a chip.
+     * </p>
+     */
+    public void testCreateReplacementChipOriginalText() {
+        // Name in quotes + email address
+        testCreateReplacementChipOriginalText("\"Android User\" <android@example.com>,");
+        // Name in quotes + email address without brackets
+        testCreateReplacementChipOriginalText("\"Android User\" android@example.com,");
+        // Name in quotes
+        testCreateReplacementChipOriginalText("\"Android User\",");
+        // Name without quotes + email address
+        testCreateReplacementChipOriginalText("Android User <android@example.com>,");
+        // Name without quotes
+        testCreateReplacementChipOriginalText("Android User,");
+        // Email address
+        testCreateReplacementChipOriginalText("<android@example.com>,");
+        // Email address without brackets
+        testCreateReplacementChipOriginalText("android@example.com,");
+    }
+
+    private void testCreateReplacementChipOriginalText(final String email) {
+        // No trailing space
+        attemptCreateReplacementChipOriginalText(email.trim());
+        // Trailing space
+        attemptCreateReplacementChipOriginalText(email.trim() + " ");
+    }
+
+    private void attemptCreateReplacementChipOriginalText(final String email) {
+        final RecipientEditTextView view = new RecipientEditTextView(getContext(), null);
+
+        view.setText(email);
+        view.mPendingChips.add(email);
+
+        view.createReplacementChip(0, email.length(), view.getText(), true);
+        // The "original text" should be the email without the comma or space(s)
+        assertEquals(email.replaceAll(",\\s*$", ""),
+                view.mTemporaryRecipients.get(0).getOriginalText().toString().trim());
+    }
+
+    public void testCreateTokenizedEntryForPhone() {
+        final String phonePattern = "[^\\d]*888[^\\d]*555[^\\d]*1234[^\\d]*";
+        final String phone1 = "8885551234";
+        final String phone2 = "888-555-1234";
+        final String phone3 = "(888) 555-1234";
+
+        final RecipientEditTextView view = new RecipientEditTextView(getContext(), null);
+        final BaseRecipientAdapter adapter = new TestBaseRecipientAdapter(getContext(), 10,
+                BaseRecipientAdapter.QUERY_TYPE_PHONE);
+        view.setAdapter(adapter);
+
+        final RecipientEntry entry1 = view.createTokenizedEntry(phone1);
+        final String destination1 = entry1.getDestination();
+        assertTrue(phone1 + " failed with " + destination1,
+                Pattern.matches(phonePattern, destination1));
+
+        final RecipientEntry entry2 = view.createTokenizedEntry(phone2);
+        final String destination2 = entry2.getDestination();
+        assertTrue(phone2 + " failed with " + destination2,
+                Pattern.matches(phonePattern, destination2));
+
+        final RecipientEntry entry3 = view.createTokenizedEntry(phone3);
+        final String destination3 = entry3.getDestination();
+        assertTrue(phone3 + " failed with " + destination3,
+                Pattern.matches(phonePattern, destination3));
+    }
 }
diff --git a/chips/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java b/chips/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
index f4b87c0..a1a1c7a 100644
--- a/chips/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
+++ b/chips/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
@@ -18,8 +18,13 @@
 
 import android.database.Cursor;
 import android.database.MatrixCursor;
+import android.net.Uri;
+import android.provider.ContactsContract.DisplayNameSources;
 import android.test.AndroidTestCase;
 
+import com.android.ex.chips.RecipientAlternatesAdapter;
+import com.android.ex.chips.RecipientEntry;
+
 public class RecipientAlternatesAdapterTest extends AndroidTestCase {
 
     public void testRemoveDuplicateDestinations() {
@@ -99,4 +104,55 @@
         assertEquals(photoUri, c.getString(6));
         assertEquals(displayNameSource, c.getInt(7));
     }
+
+    public void testGetBetterRecipient() {
+        // Ensure that if either (but not both) parameters are null, the other is returned
+        {
+            final RecipientEntry entry1 =
+                    RecipientEntry.constructFakeEntry("1@android.com", true);
+            final RecipientEntry entry2 = null;
+
+            assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
+            assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry2, entry1), entry1);
+        }
+
+        // Ensure that if only one has a display name, it is used
+        {
+            final RecipientEntry entry1 =
+                    RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
+                            "1@android.com", 0, null, 0, 0, (Uri) null, true);
+            final RecipientEntry entry2 = RecipientEntry.constructFakeEntry("1@android.com", true);
+
+            assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
+            assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry2, entry1), entry1);
+        }
+
+        // Ensure that if one has a display name different from its destination, and the other's
+        // is equal to its destination, we use the unique one
+        {
+            final RecipientEntry entry1 =
+                    RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
+                            "1@android.com", 0, null, 0, 0, (Uri) null, true);
+            final RecipientEntry entry2 =
+                    RecipientEntry.constructTopLevelEntry("2@android.com", DisplayNameSources.EMAIL,
+                            "2@android.com", 0, null, 0, 0, (Uri) null, true);
+
+            assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
+            assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry2, entry1), entry1);
+        }
+
+        // Ensure that if only one has a photo, it is used
+        {
+            final RecipientEntry entry1 =
+                    RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
+                            "1@android.com", 0, null, 0, 0, Uri.parse("http://www.android.com"),
+                            true);
+            final RecipientEntry entry2 =
+                    RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.EMAIL,
+                            "2@android.com", 0, null, 0, 0, (Uri) null, true);
+
+            assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
+            assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry2, entry1), entry1);
+        }
+    }
 }