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);
+ }
+ }
}