Merge chips/ from platform/frameworks/ex to /
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..2bca597
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,30 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-common-chips
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4
+LOCAL_SDK_VERSION := 19
+LOCAL_SRC_FILES := \
+     $(call all-java-files-under, src) \
+     $(call all-logtags-files-under, src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+##################################################
+# Build all sub-directories
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..02ea564
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.ex.chips"
+          android:versionCode="1">
+
+    <uses-sdk
+        android:minSdkVersion="11"
+        android:targetSdkVersion="19" />
+
+</manifest>
\ No newline at end of file
diff --git a/project.properties b/project.properties
new file mode 100644
index 0000000..91d2b02
--- /dev/null
+++ b/project.properties
@@ -0,0 +1,15 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19
+android.library=true
diff --git a/res/drawable-hdpi/chip_background.9.png b/res/drawable-hdpi/chip_background.9.png
new file mode 100644
index 0000000..3988da5
--- /dev/null
+++ b/res/drawable-hdpi/chip_background.9.png
Binary files differ
diff --git a/res/drawable-hdpi/chip_background_invalid.9.png b/res/drawable-hdpi/chip_background_invalid.9.png
new file mode 100644
index 0000000..01a3d95
--- /dev/null
+++ b/res/drawable-hdpi/chip_background_invalid.9.png
Binary files differ
diff --git a/res/drawable-hdpi/chip_background_selected.9.png b/res/drawable-hdpi/chip_background_selected.9.png
new file mode 100644
index 0000000..e05657f
--- /dev/null
+++ b/res/drawable-hdpi/chip_background_selected.9.png
Binary files differ
diff --git a/res/drawable-hdpi/chip_delete.png b/res/drawable-hdpi/chip_delete.png
new file mode 100644
index 0000000..75cde4a
--- /dev/null
+++ b/res/drawable-hdpi/chip_delete.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_contact_picture.png b/res/drawable-hdpi/ic_contact_picture.png
new file mode 100644
index 0000000..4c0e35e
--- /dev/null
+++ b/res/drawable-hdpi/ic_contact_picture.png
Binary files differ
diff --git a/res/drawable-mdpi/chip_background.9.png b/res/drawable-mdpi/chip_background.9.png
new file mode 100644
index 0000000..99fd037
--- /dev/null
+++ b/res/drawable-mdpi/chip_background.9.png
Binary files differ
diff --git a/res/drawable-mdpi/chip_background_invalid.9.png b/res/drawable-mdpi/chip_background_invalid.9.png
new file mode 100644
index 0000000..f90bec5
--- /dev/null
+++ b/res/drawable-mdpi/chip_background_invalid.9.png
Binary files differ
diff --git a/res/drawable-mdpi/chip_background_selected.9.png b/res/drawable-mdpi/chip_background_selected.9.png
new file mode 100644
index 0000000..308fa03
--- /dev/null
+++ b/res/drawable-mdpi/chip_background_selected.9.png
Binary files differ
diff --git a/res/drawable-mdpi/chip_delete.png b/res/drawable-mdpi/chip_delete.png
new file mode 100644
index 0000000..75cde4a
--- /dev/null
+++ b/res/drawable-mdpi/chip_delete.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_contact_picture.png b/res/drawable-mdpi/ic_contact_picture.png
new file mode 100644
index 0000000..ead9718
--- /dev/null
+++ b/res/drawable-mdpi/ic_contact_picture.png
Binary files differ
diff --git a/res/drawable-xhdpi/chip_background.9.png b/res/drawable-xhdpi/chip_background.9.png
new file mode 100644
index 0000000..72b0f22
--- /dev/null
+++ b/res/drawable-xhdpi/chip_background.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/chip_background_invalid.9.png b/res/drawable-xhdpi/chip_background_invalid.9.png
new file mode 100644
index 0000000..a9195ea
--- /dev/null
+++ b/res/drawable-xhdpi/chip_background_invalid.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/chip_background_selected.9.png b/res/drawable-xhdpi/chip_background_selected.9.png
new file mode 100644
index 0000000..abed86a
--- /dev/null
+++ b/res/drawable-xhdpi/chip_background_selected.9.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_contact_picture.png b/res/drawable-xhdpi/ic_contact_picture.png
new file mode 100644
index 0000000..05a65f6
--- /dev/null
+++ b/res/drawable-xhdpi/ic_contact_picture.png
Binary files differ
diff --git a/res/drawable/list_item_font_primary.xml b/res/drawable/list_item_font_primary.xml
new file mode 100644
index 0000000..4351905
--- /dev/null
+++ b/res/drawable/list_item_font_primary.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+   <item android:state_activated="true" android:color="@android:color/white" />
+   <item android:state_checked="true" android:color="@android:color/white" />
+   <item android:state_selected="true" android:color="@android:color/white" />
+   <item android:color="#333333"/>
+ </selector>
\ No newline at end of file
diff --git a/res/drawable/list_item_font_secondary.xml b/res/drawable/list_item_font_secondary.xml
new file mode 100644
index 0000000..78c4066
--- /dev/null
+++ b/res/drawable/list_item_font_secondary.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+ <selector xmlns:android="http://schemas.android.com/apk/res/android">
+   <item android:state_activated="true" android:color="@android:color/white" />
+   <item android:state_checked="true" android:color="@android:color/white" />
+   <item android:state_selected="true" android:color="@android:color/white" />
+   <item android:color="#777777"/>
+ </selector>
\ No newline at end of file
diff --git a/res/layout/chips_alternate_item.xml b/res/layout/chips_alternate_item.xml
new file mode 100644
index 0000000..9bc7b6d
--- /dev/null
+++ b/res/layout/chips_alternate_item.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<FrameLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <include layout="@layout/chips_recipient_dropdown_item"/>
+
+</FrameLayout>
diff --git a/res/layout/chips_recipient_dropdown_item.xml b/res/layout/chips_recipient_dropdown_item.xml
new file mode 100644
index 0000000..b02b197
--- /dev/null
+++ b/res/layout/chips_recipient_dropdown_item.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="48dip"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:background="?android:attr/activatedBackgroundIndicator">
+    <LinearLayout
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical"
+        android:orientation="vertical"
+        android:layout_weight="1">
+        <TextView android:id="@android:id/title"
+                  android:textColor="@drawable/list_item_font_primary"
+                  android:textSize="18sp"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:singleLine="true"
+                  android:ellipsize="end"
+                  style="@style/ChipTitleStyle" />
+        <TextView android:id="@android:id/text1"
+                  android:textColor="@drawable/list_item_font_secondary"
+                  android:textSize="14sp"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:singleLine="true"
+                  android:ellipsize="end"
+                  android:layout_marginTop="-4dip"
+                  style="@style/ChipSubtitleStyle" />
+    </LinearLayout>
+    <ImageView
+        android:id="@android:id/icon"
+        android:layout_width="48dip"
+        android:layout_height="48dip"
+        android:src="@drawable/ic_contact_picture"
+        android:cropToPadding="true"
+        android:scaleType="centerCrop"
+        style="@style/ChipIconStyle" />
+</LinearLayout>
diff --git a/res/layout/copy_chip_dialog_layout.xml b/res/layout/copy_chip_dialog_layout.xml
new file mode 100644
index 0000000..f131626
--- /dev/null
+++ b/res/layout/copy_chip_dialog_layout.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:text="@string/copy_email"
+    android:id="@+android:id/button1"
+    android:background="@null"
+    android:layout_gravity="left"/>
diff --git a/res/layout/more_item.xml b/res/layout/more_item.xml
new file mode 100644
index 0000000..20693d6
--- /dev/null
+++ b/res/layout/more_item.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<TextView  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/text1"
+    android:textColor="#aaaaaa"
+    android:textSize="18sp"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:text="@string/more_string"
+    android:paddingLeft="8dip"
+    android:paddingRight="8dip"
+    />
\ No newline at end of file
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
new file mode 100644
index 0000000..3ccbcba
--- /dev/null
+++ b/res/values-af/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
new file mode 100644
index 0000000..0537868
--- /dev/null
+++ b/res/values-am/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
new file mode 100644
index 0000000..3398641
--- /dev/null
+++ b/res/values-ar/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
new file mode 100644
index 0000000..c1e8567
--- /dev/null
+++ b/res/values-az-rAZ/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
+    <string name="copy_email" msgid="7869435992461603532">"E-poçt ünvanını kopyalayın"</string>
+    <string name="copy_number" msgid="530057841276106843">"Telefon nömrəsini kopyalayın"</string>
+    <string name="done" msgid="2356320650733788862">"Geri qayıt"</string>
+</resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
new file mode 100644
index 0000000..c1e8567
--- /dev/null
+++ b/res/values-az/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
+    <string name="copy_email" msgid="7869435992461603532">"E-poçt ünvanını kopyalayın"</string>
+    <string name="copy_number" msgid="530057841276106843">"Telefon nömrəsini kopyalayın"</string>
+    <string name="done" msgid="2356320650733788862">"Geri qayıt"</string>
+</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
new file mode 100644
index 0000000..06ed49e
--- /dev/null
+++ b/res/values-bg/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
new file mode 100644
index 0000000..77d2196
--- /dev/null
+++ b/res/values-ca/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
new file mode 100644
index 0000000..212d734
--- /dev/null
+++ b/res/values-cs/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
new file mode 100644
index 0000000..8196fee
--- /dev/null
+++ b/res/values-da/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 0000000..d4c2fe4
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
new file mode 100644
index 0000000..51b5ac3
--- /dev/null
+++ b/res/values-el/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..1ae784b
--- /dev/null
+++ b/res/values-en-rGB/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..1ae784b
--- /dev/null
+++ b/res/values-en-rIN/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..c63e6cb
--- /dev/null
+++ b/res/values-es-rUS/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
new file mode 100644
index 0000000..74478a8
--- /dev/null
+++ b/res/values-es/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..f32e66d
--- /dev/null
+++ b/res/values-et-rEE/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
new file mode 100644
index 0000000..e5b32ba
--- /dev/null
+++ b/res/values-fa/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
new file mode 100644
index 0000000..9893923
--- /dev/null
+++ b/res/values-fi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..758f5a8
--- /dev/null
+++ b/res/values-fr-rCA/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="8495478259330621990">"+<xliff:g id="COUNT">%1$s</xliff:g>"</string>
+    <string name="copy_email" msgid="7869435992461603532">"Copier l\'adresse de courriel"</string>
+    <string name="copy_number" msgid="530057841276106843">"Copier le numéro de téléphone"</string>
+    <string name="done" msgid="2356320650733788862">"Renvoyer"</string>
+</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
new file mode 100644
index 0000000..3d13b5f
--- /dev/null
+++ b/res/values-fr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
new file mode 100644
index 0000000..8368c0d
--- /dev/null
+++ b/res/values-hi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
new file mode 100644
index 0000000..65f2c12
--- /dev/null
+++ b/res/values-hr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
new file mode 100644
index 0000000..a18f811
--- /dev/null
+++ b/res/values-hu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..ca2695c
--- /dev/null
+++ b/res/values-hy-rAM/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
new file mode 100644
index 0000000..9c1cbe6
--- /dev/null
+++ b/res/values-in/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
new file mode 100644
index 0000000..3f87ec0
--- /dev/null
+++ b/res/values-it/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
new file mode 100644
index 0000000..d7be75a
--- /dev/null
+++ b/res/values-iw/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
new file mode 100644
index 0000000..543eb8a
--- /dev/null
+++ b/res/values-ja/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..9d24e05
--- /dev/null
+++ b/res/values-ka-rGE/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..e51c667
--- /dev/null
+++ b/res/values-km-rKH/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
new file mode 100644
index 0000000..f7884bd
--- /dev/null
+++ b/res/values-ko/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-land/dimen.xml b/res/values-land/dimen.xml
new file mode 100644
index 0000000..1ee6608
--- /dev/null
+++ b/res/values-land/dimen.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <dimen name="chip_height">26dip</dimen>
+</resources>
\ No newline at end of file
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..44912af
--- /dev/null
+++ b/res/values-lo-rLA/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
new file mode 100644
index 0000000..e85eba3
--- /dev/null
+++ b/res/values-lt/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
new file mode 100644
index 0000000..f06e4fc
--- /dev/null
+++ b/res/values-lv/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..89923c3
--- /dev/null
+++ b/res/values-mn-rMN/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..76320f9
--- /dev/null
+++ b/res/values-ms-rMY/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
new file mode 100644
index 0000000..a71348e
--- /dev/null
+++ b/res/values-nb/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
new file mode 100644
index 0000000..4710484
--- /dev/null
+++ b/res/values-ne-rNP/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
new file mode 100644
index 0000000..4710484
--- /dev/null
+++ b/res/values-ne/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
new file mode 100644
index 0000000..c4289c6
--- /dev/null
+++ b/res/values-nl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
new file mode 100644
index 0000000..8746e48
--- /dev/null
+++ b/res/values-pl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..fc991b1
--- /dev/null
+++ b/res/values-pt-rPT/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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">"Voltar"</string>
+</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
new file mode 100644
index 0000000..58a23e3
--- /dev/null
+++ b/res/values-pt/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
new file mode 100644
index 0000000..6bd8a36
--- /dev/null
+++ b/res/values-ro/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
new file mode 100644
index 0000000..0d6a2d7
--- /dev/null
+++ b/res/values-ru/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
new file mode 100644
index 0000000..313405f
--- /dev/null
+++ b/res/values-si-rLK/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
new file mode 100644
index 0000000..313405f
--- /dev/null
+++ b/res/values-si/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
new file mode 100644
index 0000000..155da99
--- /dev/null
+++ b/res/values-sk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
new file mode 100644
index 0000000..e9877dd
--- /dev/null
+++ b/res/values-sl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
new file mode 100644
index 0000000..578ca42
--- /dev/null
+++ b/res/values-sr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
new file mode 100644
index 0000000..a2a9f40
--- /dev/null
+++ b/res/values-sv/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
new file mode 100644
index 0000000..edea133
--- /dev/null
+++ b/res/values-sw/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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">"Inayofuata"</string>
+</resources>
diff --git a/res/values-sw600dp-land/dimen.xml b/res/values-sw600dp-land/dimen.xml
new file mode 100644
index 0000000..dc3aadf
--- /dev/null
+++ b/res/values-sw600dp-land/dimen.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <dimen name="chip_padding">8dip</dimen>
+    <dimen name="chip_height">32dip</dimen>
+    <dimen name="chip_text_size">14sp</dimen>
+</resources>
\ No newline at end of file
diff --git a/res/values-sw600dp/styles.xml b/res/values-sw600dp/styles.xml
new file mode 100644
index 0000000..00988a9
--- /dev/null
+++ b/res/values-sw600dp/styles.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources xmlns:tools="http://schemas.android.com/tools"
+           xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <style name="RecipientEditTextView" parent="@android:attr/autoCompleteTextViewStyle">
+        <item name="android:paddingLeft">8dip</item>
+        <item name="android:paddingRight">4dip</item>
+        <item name="android:inputType">textEmailAddress|textMultiLine</item>
+        <item name="android:imeOptions">actionNext</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
+        <item name="android:background">@null</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:dropDownVerticalOffset">0dip</item>
+        <item name="android:dropDownHorizontalOffset">-4dip</item>
+        <item name="android:textAlignment" tools:ignore="NewApi">viewStart</item>
+        <item name="android:textDirection" tools:ignore="NewApi">locale</item>
+    </style>
+</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
new file mode 100644
index 0000000..fffafd0
--- /dev/null
+++ b/res/values-th/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
new file mode 100644
index 0000000..db846ca
--- /dev/null
+++ b/res/values-tl/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
new file mode 100644
index 0000000..1e099a4
--- /dev/null
+++ b/res/values-tr/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
new file mode 100644
index 0000000..820183e
--- /dev/null
+++ b/res/values-uk/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-v17/styles-v17.xml b/res/values-v17/styles-v17.xml
new file mode 100644
index 0000000..d151a75
--- /dev/null
+++ b/res/values-v17/styles-v17.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<resources>
+    <style name="ChipTitleStyle">
+        <item name="android:paddingStart">@dimen/chip_title_padding_start</item>
+    </style>
+
+    <style name="ChipSubtitleStyle">
+        <item name="android:paddingStart">@dimen/chip_subtitle_padding_start</item>
+    </style>
+
+    <style name="ChipIconStyle">
+        <item name="android:layout_marginStart">@dimen/chip_icon_margin_start</item>
+    </style>
+</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
new file mode 100644
index 0000000..f42d837
--- /dev/null
+++ b/res/values-vi/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..2283f75
--- /dev/null
+++ b/res/values-zh-rCN/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..31a122a
--- /dev/null
+++ b/res/values-zh-rHK/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..62d71cf
--- /dev/null
+++ b/res/values-zh-rTW/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
new file mode 100644
index 0000000..9ae03ed
--- /dev/null
+++ b/res/values-zu/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<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="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>
+</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
new file mode 100644
index 0000000..d3500aa
--- /dev/null
+++ b/res/values/attrs.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+    <declare-styleable name="RecipientEditTextView">
+        <attr name="avatarPosition">
+            <enum name="end" value="0" />
+            <enum name="start" value="1" />
+        </attr>
+        <attr name="chipBackground" format="reference" />
+        <attr name="chipBackgroundPressed" format="reference" />
+        <attr name="chipDelete" format="reference" />
+        <attr name="chipFontSize" format="reference" />
+        <attr name="chipHeight" format="reference" />
+        <attr name="chipPadding" format="reference" />
+        <attr name="disableDelete" format="boolean" />
+        <attr name="invalidChipBackground" format="reference" />
+        <attr name="imageSpanAlignment">
+            <enum name="bottom" value = "0"/>
+            <enum name="baseline" value = "1"/>
+        </attr>
+    </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/res/values/dimen.xml b/res/values/dimen.xml
new file mode 100644
index 0000000..f989c86
--- /dev/null
+++ b/res/values/dimen.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources>
+    <dimen name="chip_padding">8dip</dimen>
+    <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>
+
+    <dimen name="chip_title_padding_start">8dip</dimen>
+    <dimen name="chip_subtitle_padding_start">16dip</dimen>
+    <dimen name="chip_icon_margin_start">12dip</dimen>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..3588ec3
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<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">\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] -->
+    <string name="copy_email">Copy email address</string>
+    <!-- Text displayed when the user long presses on a chip to copy the recipient's phone number.
+         [CHAR LIMIT=200] -->
+    <string name="copy_number">Copy phone number</string>
+    <!-- Text displayed in the enter key slot when the recipientedittextview has focus.
+         [CHAR LIMIT=12] -->
+    <string name="done">Return</string>
+</resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
new file mode 100644
index 0000000..9b60cde
--- /dev/null
+++ b/res/values/styles.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources xmlns:tools="http://schemas.android.com/tools"
+           xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <style name="RecipientEditTextView" parent="@android:attr/autoCompleteTextViewStyle">
+        <item name="android:inputType">textEmailAddress|textMultiLine</item>
+        <item name="android:imeOptions">actionNext|flagNoFullscreen</item>
+        <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
+        <item name="android:background">@null</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:dropDownVerticalOffset">-6dip</item>
+        <item name="android:dropDownHorizontalOffset">-16dip</item>
+        <item name="android:minHeight">48dip</item>
+        <item name="android:lineSpacingExtra">@dimen/line_spacing_extra</item>
+        <item name="android:textAlignment" tools:ignore="NewApi">viewStart</item>
+        <item name="android:textDirection" tools:ignore="NewApi">locale</item>
+    </style>
+
+    <style name="ChipTitleStyle">
+        <item name="android:paddingLeft">@dimen/chip_title_padding_start</item>
+    </style>
+
+    <style name="ChipSubtitleStyle">
+        <item name="android:paddingLeft">@dimen/chip_subtitle_padding_start</item>
+    </style>
+
+    <style name="ChipIconStyle">
+        <item name="android:layout_marginLeft">@dimen/chip_icon_margin_start</item>
+    </style>
+</resources>
diff --git a/sample/Android.mk b/sample/Android.mk
new file mode 100644
index 0000000..978aa7f
--- /dev/null
+++ b/sample/Android.mk
@@ -0,0 +1,44 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+# Include res dir from chips
+chips_dir := ../res
+res_dirs := res $(chips_dir)
+
+##################################################
+# Build APK
+include $(CLEAR_VARS)
+
+src_dirs := src
+LOCAL_PACKAGE_NAME := ChipsSample
+
+LOCAL_STATIC_JAVA_LIBRARIES += android-common-chips
+
+LOCAL_SDK_VERSION := 18
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+        $(call all-logtags-files-under, $(src_dirs))
+LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, $(res_dirs))
+LOCAL_AAPT_FLAGS := --auto-add-overlay
+LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.chips
+
+include $(BUILD_PACKAGE)
+
+
+##################################################
+# Build all sub-directories
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/sample/AndroidManifest.xml b/sample/AndroidManifest.xml
new file mode 100644
index 0000000..a490e29
--- /dev/null
+++ b/sample/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.ex.chips.sample"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="11"
+        android:targetSdkVersion="18" />
+
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@android:style/Theme.Holo.Light" >
+        <activity
+            android:name="com.android.ex.chips.sample.MainActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/sample/res/drawable-hdpi/ic_launcher.png b/sample/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/sample/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/sample/res/drawable-mdpi/ic_launcher.png b/sample/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/sample/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/sample/res/drawable-xhdpi/ic_launcher.png b/sample/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/sample/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/sample/res/layout/activity_main.xml b/sample/res/layout/activity_main.xml
new file mode 100644
index 0000000..01a9ff3
--- /dev/null
+++ b/sample/res/layout/activity_main.xml
@@ -0,0 +1,36 @@
+<!-- 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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".MainActivity" >
+
+    <com.android.ex.chips.RecipientEditTextView
+        android:id="@+id/email_retv"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/email_addresses"
+        android:minHeight="58dp" />
+
+    <com.android.ex.chips.RecipientEditTextView
+        android:id="@+id/phone_retv"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:hint="@string/phone_numbers"
+        android:minHeight="58dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/sample/res/values-af/strings.xml b/sample/res/values-af/strings.xml
new file mode 100644
index 0000000..b29edff
--- /dev/null
+++ b/sample/res/values-af/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-posadresse"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Foonnommers"</string>
+</resources>
diff --git a/sample/res/values-am/strings.xml b/sample/res/values-am/strings.xml
new file mode 100644
index 0000000..1da685a
--- /dev/null
+++ b/sample/res/values-am/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"የኢሜይል አድራሻዎች"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"ስልክ ቁጥሮች"</string>
+</resources>
diff --git a/sample/res/values-ar/strings.xml b/sample/res/values-ar/strings.xml
new file mode 100644
index 0000000..09f9e76
--- /dev/null
+++ b/sample/res/values-ar/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"عناوين البريد الإلكتروني"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"أرقام الهواتف"</string>
+</resources>
diff --git a/sample/res/values-bg/strings.xml b/sample/res/values-bg/strings.xml
new file mode 100644
index 0000000..24d690b
--- /dev/null
+++ b/sample/res/values-bg/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Имейл адреси"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Телефонни номера"</string>
+</resources>
diff --git a/sample/res/values-ca/strings.xml b/sample/res/values-ca/strings.xml
new file mode 100644
index 0000000..054ee13
--- /dev/null
+++ b/sample/res/values-ca/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Adreces electròniques"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Números de telèfon"</string>
+</resources>
diff --git a/sample/res/values-cs/strings.xml b/sample/res/values-cs/strings.xml
new file mode 100644
index 0000000..fcdb9cf
--- /dev/null
+++ b/sample/res/values-cs/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-mailové adresy"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Telefonní čísla"</string>
+</resources>
diff --git a/sample/res/values-da/strings.xml b/sample/res/values-da/strings.xml
new file mode 100644
index 0000000..41279d3
--- /dev/null
+++ b/sample/res/values-da/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-mailadresser"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Telefonnumre"</string>
+</resources>
diff --git a/sample/res/values-de/strings.xml b/sample/res/values-de/strings.xml
new file mode 100644
index 0000000..c234ec4
--- /dev/null
+++ b/sample/res/values-de/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-Mail-Adressen"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Telefonnummern"</string>
+</resources>
diff --git a/sample/res/values-el/strings.xml b/sample/res/values-el/strings.xml
new file mode 100644
index 0000000..ea827d9
--- /dev/null
+++ b/sample/res/values-el/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Διευθύνσεις ηλεκτρονικού ταχυδρομείου"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Αριθμοί τηλεφώνου"</string>
+</resources>
diff --git a/sample/res/values-en-rGB/strings.xml b/sample/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..8cf71de
--- /dev/null
+++ b/sample/res/values-en-rGB/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Email Addresses"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Phone Numbers"</string>
+</resources>
diff --git a/sample/res/values-en-rIN/strings.xml b/sample/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..8cf71de
--- /dev/null
+++ b/sample/res/values-en-rIN/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Email Addresses"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Phone Numbers"</string>
+</resources>
diff --git a/sample/res/values-es-rUS/strings.xml b/sample/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..270a120
--- /dev/null
+++ b/sample/res/values-es-rUS/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Direcciones de correo electrónico"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Números de teléfono"</string>
+</resources>
diff --git a/sample/res/values-es/strings.xml b/sample/res/values-es/strings.xml
new file mode 100644
index 0000000..270a120
--- /dev/null
+++ b/sample/res/values-es/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Direcciones de correo electrónico"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Números de teléfono"</string>
+</resources>
diff --git a/sample/res/values-et-rEE/strings.xml b/sample/res/values-et-rEE/strings.xml
new file mode 100644
index 0000000..e343cf4
--- /dev/null
+++ b/sample/res/values-et-rEE/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-posti aadressid"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Telefoninumbrid"</string>
+</resources>
diff --git a/sample/res/values-fa/strings.xml b/sample/res/values-fa/strings.xml
new file mode 100644
index 0000000..d31e347
--- /dev/null
+++ b/sample/res/values-fa/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"آدرس‌های ایمیل"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"شماره‌ تلفن‌ها"</string>
+</resources>
diff --git a/sample/res/values-fi/strings.xml b/sample/res/values-fi/strings.xml
new file mode 100644
index 0000000..348473d
--- /dev/null
+++ b/sample/res/values-fi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Sähköpostiosoitteet"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Puhelinnumerot"</string>
+</resources>
diff --git a/sample/res/values-fr-rCA/strings.xml b/sample/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..68dcf33
--- /dev/null
+++ b/sample/res/values-fr-rCA/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Adresses de courriel"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Numéros de téléphone"</string>
+</resources>
diff --git a/sample/res/values-fr/strings.xml b/sample/res/values-fr/strings.xml
new file mode 100644
index 0000000..e0bf7ac
--- /dev/null
+++ b/sample/res/values-fr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Adresses e-mail"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Numéros de téléphone"</string>
+</resources>
diff --git a/sample/res/values-hi/strings.xml b/sample/res/values-hi/strings.xml
new file mode 100644
index 0000000..20a8435
--- /dev/null
+++ b/sample/res/values-hi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"ईमेल पते"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"फ़ोन नंबर"</string>
+</resources>
diff --git a/sample/res/values-hr/strings.xml b/sample/res/values-hr/strings.xml
new file mode 100644
index 0000000..d6da228
--- /dev/null
+++ b/sample/res/values-hr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-adrese"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Telefonski brojevi"</string>
+</resources>
diff --git a/sample/res/values-hu/strings.xml b/sample/res/values-hu/strings.xml
new file mode 100644
index 0000000..b2ba000
--- /dev/null
+++ b/sample/res/values-hu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-mail címek"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Telefonszámok"</string>
+</resources>
diff --git a/sample/res/values-hy-rAM/strings.xml b/sample/res/values-hy-rAM/strings.xml
new file mode 100644
index 0000000..09ad981
--- /dev/null
+++ b/sample/res/values-hy-rAM/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Էլփոստի հասցեներ"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Հեռախոսահամարներ"</string>
+</resources>
diff --git a/sample/res/values-in/strings.xml b/sample/res/values-in/strings.xml
new file mode 100644
index 0000000..036e97d
--- /dev/null
+++ b/sample/res/values-in/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Alamat Email"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Nomor Telepon"</string>
+</resources>
diff --git a/sample/res/values-it/strings.xml b/sample/res/values-it/strings.xml
new file mode 100644
index 0000000..67bca55
--- /dev/null
+++ b/sample/res/values-it/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Indirizzi email"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Numeri di telefono"</string>
+</resources>
diff --git a/sample/res/values-iw/strings.xml b/sample/res/values-iw/strings.xml
new file mode 100644
index 0000000..7ead2ae
--- /dev/null
+++ b/sample/res/values-iw/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"כתובות דוא\"ל"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"מספרי טלפון"</string>
+</resources>
diff --git a/sample/res/values-ja/strings.xml b/sample/res/values-ja/strings.xml
new file mode 100644
index 0000000..6b0c0a7
--- /dev/null
+++ b/sample/res/values-ja/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"メールアドレス"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"電話番号"</string>
+</resources>
diff --git a/sample/res/values-ka-rGE/strings.xml b/sample/res/values-ka-rGE/strings.xml
new file mode 100644
index 0000000..ec7f764
--- /dev/null
+++ b/sample/res/values-ka-rGE/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"ელფოსტის მისამართები"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"ტელეფონის ნომრები"</string>
+</resources>
diff --git a/sample/res/values-km-rKH/strings.xml b/sample/res/values-km-rKH/strings.xml
new file mode 100644
index 0000000..70ca37e
--- /dev/null
+++ b/sample/res/values-km-rKH/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"អាសយដ្ឋាន​អ៊ីមែល"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"លេខទូរស័ព្ទ"</string>
+</resources>
diff --git a/sample/res/values-ko/strings.xml b/sample/res/values-ko/strings.xml
new file mode 100644
index 0000000..32b49a7
--- /dev/null
+++ b/sample/res/values-ko/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"이메일 주소"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"전화번호"</string>
+</resources>
diff --git a/sample/res/values-lo-rLA/strings.xml b/sample/res/values-lo-rLA/strings.xml
new file mode 100644
index 0000000..6ccf492
--- /dev/null
+++ b/sample/res/values-lo-rLA/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"ທີ່ຢູ່ອີເມວ"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"ເບີໂທລະສັບ:"</string>
+</resources>
diff --git a/sample/res/values-lt/strings.xml b/sample/res/values-lt/strings.xml
new file mode 100644
index 0000000..ce73b40
--- /dev/null
+++ b/sample/res/values-lt/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"El. pašto adresai"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Telefonų numeriai"</string>
+</resources>
diff --git a/sample/res/values-lv/strings.xml b/sample/res/values-lv/strings.xml
new file mode 100644
index 0000000..6dd6ffe
--- /dev/null
+++ b/sample/res/values-lv/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-pasta adreses"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Tālruņa numuri"</string>
+</resources>
diff --git a/sample/res/values-mn-rMN/strings.xml b/sample/res/values-mn-rMN/strings.xml
new file mode 100644
index 0000000..1398a43
--- /dev/null
+++ b/sample/res/values-mn-rMN/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Имэйл хаягууд"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Утасны дугаарууд"</string>
+</resources>
diff --git a/sample/res/values-ms-rMY/strings.xml b/sample/res/values-ms-rMY/strings.xml
new file mode 100644
index 0000000..696871d
--- /dev/null
+++ b/sample/res/values-ms-rMY/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Alamat E-mel"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Nombor Telefon"</string>
+</resources>
diff --git a/sample/res/values-nb/strings.xml b/sample/res/values-nb/strings.xml
new file mode 100644
index 0000000..2d5e56a
--- /dev/null
+++ b/sample/res/values-nb/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-postadresser"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Telefonnumre"</string>
+</resources>
diff --git a/sample/res/values-nl/strings.xml b/sample/res/values-nl/strings.xml
new file mode 100644
index 0000000..f47ff3a
--- /dev/null
+++ b/sample/res/values-nl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-mailadressen"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Telefoonnummers"</string>
+</resources>
diff --git a/sample/res/values-pl/strings.xml b/sample/res/values-pl/strings.xml
new file mode 100644
index 0000000..573d22e
--- /dev/null
+++ b/sample/res/values-pl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Adresy e-mail"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Numery telefonów"</string>
+</resources>
diff --git a/sample/res/values-pt-rPT/strings.xml b/sample/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..675e2eb
--- /dev/null
+++ b/sample/res/values-pt-rPT/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Endereços de email"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Números de telefone"</string>
+</resources>
diff --git a/sample/res/values-pt/strings.xml b/sample/res/values-pt/strings.xml
new file mode 100644
index 0000000..5f8e346
--- /dev/null
+++ b/sample/res/values-pt/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Endereços de e-mail"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Números de telefone"</string>
+</resources>
diff --git a/sample/res/values-ro/strings.xml b/sample/res/values-ro/strings.xml
new file mode 100644
index 0000000..eb71526
--- /dev/null
+++ b/sample/res/values-ro/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Adrese de e-mail"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Numere de telefon"</string>
+</resources>
diff --git a/sample/res/values-ru/strings.xml b/sample/res/values-ru/strings.xml
new file mode 100644
index 0000000..215e40a
--- /dev/null
+++ b/sample/res/values-ru/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Адреса эл. почты"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Номера телефонов"</string>
+</resources>
diff --git a/sample/res/values-sk/strings.xml b/sample/res/values-sk/strings.xml
new file mode 100644
index 0000000..11d71c5
--- /dev/null
+++ b/sample/res/values-sk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-mailové adresy"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Telefónne čísla"</string>
+</resources>
diff --git a/sample/res/values-sl/strings.xml b/sample/res/values-sl/strings.xml
new file mode 100644
index 0000000..1e1c566
--- /dev/null
+++ b/sample/res/values-sl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-poštni naslovi"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Telefonske številke"</string>
+</resources>
diff --git a/sample/res/values-sr/strings.xml b/sample/res/values-sr/strings.xml
new file mode 100644
index 0000000..4bca249
--- /dev/null
+++ b/sample/res/values-sr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Адресе е-поште"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Бројеви телефона"</string>
+</resources>
diff --git a/sample/res/values-sv/strings.xml b/sample/res/values-sv/strings.xml
new file mode 100644
index 0000000..d7f6773
--- /dev/null
+++ b/sample/res/values-sv/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-postadresser"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Telefonnummer"</string>
+</resources>
diff --git a/sample/res/values-sw/strings.xml b/sample/res/values-sw/strings.xml
new file mode 100644
index 0000000..62f4048
--- /dev/null
+++ b/sample/res/values-sw/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Anwani za Barua Pepe"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Nambari za Simu"</string>
+</resources>
diff --git a/sample/res/values-th/strings.xml b/sample/res/values-th/strings.xml
new file mode 100644
index 0000000..3658256
--- /dev/null
+++ b/sample/res/values-th/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"ที่อยู่อีเมล"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"หมายเลขโทรศัพท์"</string>
+</resources>
diff --git a/sample/res/values-tl/strings.xml b/sample/res/values-tl/strings.xml
new file mode 100644
index 0000000..96cd1c7
--- /dev/null
+++ b/sample/res/values-tl/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Mga Email Address"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Mga Numero ng Telepono"</string>
+</resources>
diff --git a/sample/res/values-tr/strings.xml b/sample/res/values-tr/strings.xml
new file mode 100644
index 0000000..13395ab
--- /dev/null
+++ b/sample/res/values-tr/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"E-posta Adresleri"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Telefon Numaraları"</string>
+</resources>
diff --git a/sample/res/values-uk/strings.xml b/sample/res/values-uk/strings.xml
new file mode 100644
index 0000000..cdf5837
--- /dev/null
+++ b/sample/res/values-uk/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Електронні адреси"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Номери телефонів"</string>
+</resources>
diff --git a/sample/res/values-vi/strings.xml b/sample/res/values-vi/strings.xml
new file mode 100644
index 0000000..a93a8c9
--- /dev/null
+++ b/sample/res/values-vi/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Địa chỉ email"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Số điện thoại"</string>
+</resources>
diff --git a/sample/res/values-zh-rCN/strings.xml b/sample/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..40f6ef1
--- /dev/null
+++ b/sample/res/values-zh-rCN/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"电子邮件地址"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"电话号码"</string>
+</resources>
diff --git a/sample/res/values-zh-rHK/strings.xml b/sample/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..18c7f2e
--- /dev/null
+++ b/sample/res/values-zh-rHK/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"電郵地址"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"電話號碼"</string>
+</resources>
diff --git a/sample/res/values-zh-rTW/strings.xml b/sample/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..971df5d
--- /dev/null
+++ b/sample/res/values-zh-rTW/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"電子郵件地址"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"電話號碼"</string>
+</resources>
diff --git a/sample/res/values-zu/strings.xml b/sample/res/values-zu/strings.xml
new file mode 100644
index 0000000..de6722b
--- /dev/null
+++ b/sample/res/values-zu/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--  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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="email_addresses" msgid="5320415175940315400">"Amakheli we-imeyili"</string>
+    <string name="phone_numbers" msgid="7836326833170390688">"Izinombolo zefoni"</string>
+</resources>
diff --git a/sample/res/values/strings.xml b/sample/res/values/strings.xml
new file mode 100644
index 0000000..a40b20d
--- /dev/null
+++ b/sample/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+
+    <string name="app_name" translatable="false">Chips Sample</string>
+    <string name="email_addresses">Email Addresses</string>
+    <string name="phone_numbers">Phone Numbers</string>
+
+</resources>
diff --git a/sample/src/com/android/ex/chips/sample/MainActivity.java b/sample/src/com/android/ex/chips/sample/MainActivity.java
new file mode 100644
index 0000000..0622e65
--- /dev/null
+++ b/sample/src/com/android/ex/chips/sample/MainActivity.java
@@ -0,0 +1,45 @@
+/*
+ * 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.sample;
+
+import android.os.Bundle;
+import android.text.util.Rfc822Tokenizer;
+import android.widget.MultiAutoCompleteTextView;
+import android.app.Activity;
+
+import com.android.ex.chips.BaseRecipientAdapter;
+import com.android.ex.chips.RecipientEditTextView;
+
+public class MainActivity extends Activity {
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+
+        final RecipientEditTextView emailRetv =
+                (RecipientEditTextView) findViewById(R.id.email_retv);
+        emailRetv.setTokenizer(new Rfc822Tokenizer());
+        emailRetv.setAdapter(new BaseRecipientAdapter(this) { });
+
+        final RecipientEditTextView phoneRetv =
+                (RecipientEditTextView) findViewById(R.id.phone_retv);
+        phoneRetv.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
+        phoneRetv.setAdapter(
+                new BaseRecipientAdapter(BaseRecipientAdapter.QUERY_TYPE_PHONE, this) { });
+    }
+
+}
diff --git a/src/com/android/ex/chips/AccountSpecifier.java b/src/com/android/ex/chips/AccountSpecifier.java
new file mode 100644
index 0000000..5eb8314
--- /dev/null
+++ b/src/com/android/ex/chips/AccountSpecifier.java
@@ -0,0 +1,28 @@
+/*
+ * 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.accounts.Account;
+
+/**
+ * The AccountSpecificAdapter interface describes an Adapter
+ * that can take an account to retrieve information tied to
+ * a specific account.
+ */
+public interface AccountSpecifier {
+    public void setAccount(Account account);
+}
diff --git a/src/com/android/ex/chips/BaseRecipientAdapter.java b/src/com/android/ex/chips/BaseRecipientAdapter.java
new file mode 100644
index 0000000..468e168
--- /dev/null
+++ b/src/com/android/ex/chips/BaseRecipientAdapter.java
@@ -0,0 +1,1005 @@
+/*
+ * 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.accounts.Account;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.Directory;
+import android.support.v4.util.LruCache;
+import android.text.TextUtils;
+import android.text.util.Rfc822Token;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AutoCompleteTextView;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Adapter for showing a recipient list.
+ */
+public class BaseRecipientAdapter extends BaseAdapter implements Filterable, AccountSpecifier {
+    private static final String TAG = "BaseRecipientAdapter";
+
+    private static final boolean DEBUG = false;
+
+    /**
+     * The preferred number of results to be retrieved. This number may be
+     * exceeded if there are several directories configured, because we will use
+     * the same limit for all directories.
+     */
+    private static final int DEFAULT_PREFERRED_MAX_RESULT_COUNT = 10;
+
+    /**
+     * The number of extra entries requested to allow for duplicates. Duplicates
+     * are removed from the overall result.
+     */
+    static final int ALLOWANCE_FOR_DUPLICATES = 5;
+
+    // This is ContactsContract.PRIMARY_ACCOUNT_NAME. Available from ICS as hidden
+    static final String PRIMARY_ACCOUNT_NAME = "name_for_primary_account";
+    // This is ContactsContract.PRIMARY_ACCOUNT_TYPE. Available from ICS as hidden
+    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;
+
+    /**
+     * The "Waiting for more contacts" message will be displayed if search is not complete
+     * within this many milliseconds.
+     */
+    private static final int MESSAGE_SEARCH_PENDING_DELAY = 1000;
+    /** Used to prepare "Waiting for more contacts" message. */
+    private static final int MESSAGE_SEARCH_PENDING = 1;
+
+    public static final int QUERY_TYPE_EMAIL = 0;
+    public static final int QUERY_TYPE_PHONE = 1;
+
+    private final Queries.Query mQuery;
+    private final int mQueryType;
+
+    /**
+     * Model object for a {@link Directory} row.
+     */
+    public final static class DirectorySearchParams {
+        public long directoryId;
+        public String directoryType;
+        public String displayName;
+        public String accountName;
+        public String accountType;
+        public CharSequence constraint;
+        public DirectoryFilter filter;
+    }
+
+    private static class PhotoQuery {
+        public static final String[] PROJECTION = {
+            Photo.PHOTO
+        };
+
+        public static final int PHOTO = 0;
+    }
+
+    protected static class DirectoryListQuery {
+
+        public static final Uri URI =
+                Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, "directories");
+        public static final String[] PROJECTION = {
+            Directory._ID,              // 0
+            Directory.ACCOUNT_NAME,     // 1
+            Directory.ACCOUNT_TYPE,     // 2
+            Directory.DISPLAY_NAME,     // 3
+            Directory.PACKAGE_NAME,     // 4
+            Directory.TYPE_RESOURCE_ID, // 5
+        };
+
+        public static final int ID = 0;
+        public static final int ACCOUNT_NAME = 1;
+        public static final int ACCOUNT_TYPE = 2;
+        public static final int DISPLAY_NAME = 3;
+        public static final int PACKAGE_NAME = 4;
+        public static final int TYPE_RESOURCE_ID = 5;
+    }
+
+    /** Used to temporarily hold results in Cursor objects. */
+    protected static class TemporaryEntry {
+        public final String displayName;
+        public final String destination;
+        public final int destinationType;
+        public final String destinationLabel;
+        public final long contactId;
+        public final Long directoryId;
+        public final long dataId;
+        public final String thumbnailUriString;
+        public final int displayNameSource;
+        public final String lookupKey;
+
+        public TemporaryEntry(
+                String displayName,
+                String destination,
+                int destinationType,
+                String destinationLabel,
+                long contactId,
+                Long directoryId,
+                long dataId,
+                String thumbnailUriString,
+                int displayNameSource,
+                String lookupKey) {
+            this.displayName = displayName;
+            this.destination = destination;
+            this.destinationType = destinationType;
+            this.destinationLabel = destinationLabel;
+            this.contactId = contactId;
+            this.directoryId = directoryId;
+            this.dataId = dataId;
+            this.thumbnailUriString = thumbnailUriString;
+            this.displayNameSource = displayNameSource;
+            this.lookupKey = lookupKey;
+        }
+
+        public TemporaryEntry(Cursor cursor, Long directoryId) {
+            this.displayName = cursor.getString(Queries.Query.NAME);
+            this.destination = cursor.getString(Queries.Query.DESTINATION);
+            this.destinationType = cursor.getInt(Queries.Query.DESTINATION_TYPE);
+            this.destinationLabel = cursor.getString(Queries.Query.DESTINATION_LABEL);
+            this.contactId = cursor.getLong(Queries.Query.CONTACT_ID);
+            this.directoryId = directoryId;
+            this.dataId = cursor.getLong(Queries.Query.DATA_ID);
+            this.thumbnailUriString = cursor.getString(Queries.Query.PHOTO_THUMBNAIL_URI);
+            this.displayNameSource = cursor.getInt(Queries.Query.DISPLAY_NAME_SOURCE);
+            this.lookupKey = cursor.getString(Queries.Query.LOOKUP_KEY);
+        }
+    }
+
+    /**
+     * Used to pass results from {@link DefaultFilter#performFiltering(CharSequence)} to
+     * {@link DefaultFilter#publishResults(CharSequence, android.widget.Filter.FilterResults)}
+     */
+    private static class DefaultFilterResult {
+        public final List<RecipientEntry> entries;
+        public final LinkedHashMap<Long, List<RecipientEntry>> entryMap;
+        public final List<RecipientEntry> nonAggregatedEntries;
+        public final Set<String> existingDestinations;
+        public final List<DirectorySearchParams> paramsList;
+
+        public DefaultFilterResult(List<RecipientEntry> entries,
+                LinkedHashMap<Long, List<RecipientEntry>> entryMap,
+                List<RecipientEntry> nonAggregatedEntries,
+                Set<String> existingDestinations,
+                List<DirectorySearchParams> paramsList) {
+            this.entries = entries;
+            this.entryMap = entryMap;
+            this.nonAggregatedEntries = nonAggregatedEntries;
+            this.existingDestinations = existingDestinations;
+            this.paramsList = paramsList;
+        }
+    }
+
+    /**
+     * An asynchronous filter used for loading two data sets: email rows from the local
+     * contact provider and the list of {@link Directory}'s.
+     */
+    private final class DefaultFilter extends Filter {
+
+        @Override
+        protected FilterResults performFiltering(CharSequence constraint) {
+            if (DEBUG) {
+                Log.d(TAG, "start filtering. constraint: " + constraint + ", thread:"
+                        + Thread.currentThread());
+            }
+
+            final FilterResults results = new FilterResults();
+            Cursor defaultDirectoryCursor = null;
+            Cursor directoryCursor = null;
+
+            if (TextUtils.isEmpty(constraint)) {
+                clearTempEntries();
+                // Return empty results.
+                return results;
+            }
+
+            try {
+                defaultDirectoryCursor = doQuery(constraint, mPreferredMaxResultCount,
+                        null /* directoryId */);
+
+                if (defaultDirectoryCursor == null) {
+                    if (DEBUG) {
+                        Log.w(TAG, "null cursor returned for default Email filter query.");
+                    }
+                } else {
+                    // These variables will become mEntries, mEntryMap, mNonAggregatedEntries, and
+                    // mExistingDestinations. Here we shouldn't use those member variables directly
+                    // since this method is run outside the UI thread.
+                    final LinkedHashMap<Long, List<RecipientEntry>> entryMap =
+                            new LinkedHashMap<Long, List<RecipientEntry>>();
+                    final List<RecipientEntry> nonAggregatedEntries =
+                            new ArrayList<RecipientEntry>();
+                    final Set<String> existingDestinations = new HashSet<String>();
+
+                    while (defaultDirectoryCursor.moveToNext()) {
+                        // Note: At this point each entry doesn't contain any photo
+                        // (thus getPhotoBytes() returns null).
+                        putOneEntry(new TemporaryEntry(defaultDirectoryCursor,
+                                null /* directoryId */),
+                                true, entryMap, nonAggregatedEntries, existingDestinations);
+                    }
+
+                    // We'll copy this result to mEntry in publicResults() (run in the UX thread).
+                    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.
+                    final int limit = mPreferredMaxResultCount - existingDestinations.size();
+                    final List<DirectorySearchParams> paramsList;
+                    if (limit > 0) {
+                        if (DEBUG) {
+                            Log.d(TAG, "More entries should be needed (current: "
+                                    + existingDestinations.size()
+                                    + ", remaining limit: " + limit + ") ");
+                        }
+                        directoryCursor = mContentResolver.query(
+                                DirectoryListQuery.URI, DirectoryListQuery.PROJECTION,
+                                null, null, null);
+                        paramsList = setupOtherDirectories(mContext, directoryCursor, mAccount);
+                    } else {
+                        // We don't need to search other directories.
+                        paramsList = null;
+                    }
+
+                    results.values = new DefaultFilterResult(
+                            entries, entryMap, nonAggregatedEntries,
+                            existingDestinations, paramsList);
+                    results.count = 1;
+                }
+            } finally {
+                if (defaultDirectoryCursor != null) {
+                    defaultDirectoryCursor.close();
+                }
+                if (directoryCursor != null) {
+                    directoryCursor.close();
+                }
+            }
+            return results;
+        }
+
+        @Override
+        protected void publishResults(final CharSequence constraint, FilterResults results) {
+            // If a user types a string very quickly and database is slow, "constraint" refers to
+            // an older text which shows inconsistent results for users obsolete (b/4998713).
+            // 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.
+                if (defaultFilterResult.paramsList != null) {
+                    final int limit = mPreferredMaxResultCount -
+                            defaultFilterResult.existingDestinations.size();
+                    startSearchOtherDirectories(constraint, defaultFilterResult.paramsList, limit);
+                }
+            }
+
+        }
+
+        @Override
+        public CharSequence convertResultToString(Object resultValue) {
+            final RecipientEntry entry = (RecipientEntry)resultValue;
+            final String displayName = entry.getDisplayName();
+            final String emailAddress = entry.getDestination();
+            if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, emailAddress)) {
+                 return emailAddress;
+            } else {
+                return new Rfc822Token(displayName, emailAddress, null).toString();
+            }
+        }
+    }
+
+    /**
+     * An asynchronous filter that performs search in a particular directory.
+     */
+    protected class DirectoryFilter extends Filter {
+        private final DirectorySearchParams mParams;
+        private int mLimit;
+
+        public DirectoryFilter(DirectorySearchParams params) {
+            mParams = params;
+        }
+
+        public synchronized void setLimit(int limit) {
+            this.mLimit = limit;
+        }
+
+        public synchronized int getLimit() {
+            return this.mLimit;
+        }
+
+        @Override
+        protected FilterResults performFiltering(CharSequence constraint) {
+            if (DEBUG) {
+                Log.d(TAG, "DirectoryFilter#performFiltering. directoryId: " + mParams.directoryId
+                        + ", constraint: " + constraint + ", thread: " + Thread.currentThread());
+            }
+            final FilterResults results = new FilterResults();
+            results.values = null;
+            results.count = 0;
+
+            if (!TextUtils.isEmpty(constraint)) {
+                final ArrayList<TemporaryEntry> tempEntries = new ArrayList<TemporaryEntry>();
+
+                Cursor cursor = null;
+                try {
+                    // We don't want to pass this Cursor object to UI thread (b/5017608).
+                    // Assuming the result should contain fairly small results (at most ~10),
+                    // We just copy everything to local structure.
+                    cursor = doQuery(constraint, getLimit(), mParams.directoryId);
+
+                    if (cursor != null) {
+                        while (cursor.moveToNext()) {
+                            tempEntries.add(new TemporaryEntry(cursor, mParams.directoryId));
+                        }
+                    }
+                } finally {
+                    if (cursor != null) {
+                        cursor.close();
+                    }
+                }
+                if (!tempEntries.isEmpty()) {
+                    results.values = tempEntries;
+                    results.count = 1;
+                }
+            }
+
+            if (DEBUG) {
+                Log.v(TAG, "finished loading directory \"" + mParams.displayName + "\"" +
+                        " with query " + constraint);
+            }
+
+            return results;
+        }
+
+        @Override
+        protected void publishResults(final CharSequence constraint, FilterResults results) {
+            if (DEBUG) {
+                Log.d(TAG, "DirectoryFilter#publishResult. constraint: " + constraint
+                        + ", mCurrentConstraint: " + mCurrentConstraint);
+            }
+            mDelayedMessageHandler.removeDelayedLoadMessage();
+            // Check if the received result matches the current constraint
+            // If not - the user must have continued typing after the request was issued, which
+            // means several member variables (like mRemainingDirectoryLoad) are already
+            // overwritten so shouldn't be touched here anymore.
+            if (TextUtils.equals(constraint, mCurrentConstraint)) {
+                if (results.count > 0) {
+                    @SuppressWarnings("unchecked")
+                    final ArrayList<TemporaryEntry> tempEntries =
+                            (ArrayList<TemporaryEntry>) results.values;
+
+                    for (TemporaryEntry tempEntry : tempEntries) {
+                        putOneEntry(tempEntry, mParams.directoryId == Directory.DEFAULT,
+                                mEntryMap, mNonAggregatedEntries, mExistingDestinations);
+                    }
+                }
+
+                // If there are remaining directories, set up delayed message again.
+                mRemainingDirectoryCount--;
+                if (mRemainingDirectoryCount > 0) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Resend delayed load message. Current mRemainingDirectoryLoad: "
+                                + mRemainingDirectoryCount);
+                    }
+                    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(mEntryMap, mNonAggregatedEntries));
+        }
+    }
+
+    private final Context mContext;
+    private final ContentResolver mContentResolver;
+    private final LayoutInflater mInflater;
+    private Account mAccount;
+    private final int mPreferredMaxResultCount;
+    private DropdownChipLayouter mDropdownChipLayouter;
+
+    /**
+     * {@link #mEntries} is responsible for showing every result for this Adapter. To
+     * construct it, we use {@link #mEntryMap}, {@link #mNonAggregatedEntries}, and
+     * {@link #mExistingDestinations}.
+     *
+     * First, each destination (an email address or a phone number) with a valid contactId is
+     * inserted into {@link #mEntryMap} and grouped by the contactId. Destinations without valid
+     * contactId (possible if they aren't in local storage) are stored in
+     * {@link #mNonAggregatedEntries}.
+     * Duplicates are removed using {@link #mExistingDestinations}.
+     *
+     * After having all results from Cursor objects, all destinations in mEntryMap are copied to
+     * {@link #mEntries}. If the number of destinations is not enough (i.e. less than
+     * {@link #mPreferredMaxResultCount}), destinations in mNonAggregatedEntries are also used.
+     *
+     * These variables are only used in UI thread, thus should not be touched in
+     * performFiltering() methods.
+     */
+    private LinkedHashMap<Long, List<RecipientEntry>> mEntryMap;
+    private List<RecipientEntry> mNonAggregatedEntries;
+    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;
+
+    /**
+     * Used to ignore asynchronous queries with a different constraint, which may happen when
+     * users type characters quickly.
+     */
+    private CharSequence mCurrentConstraint;
+
+    private final LruCache<Uri, byte[]> mPhotoCacheMap;
+
+    /**
+     * Handler specific for maintaining "Waiting for more contacts" message, which will be shown
+     * when:
+     * - there are directories to be searched
+     * - results from directories are slow to come
+     */
+    private final class DelayedMessageHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            if (mRemainingDirectoryCount > 0) {
+                updateEntries(constructEntryList(mEntryMap, mNonAggregatedEntries));
+            }
+        }
+
+        public void sendDelayedLoadMessage() {
+            sendMessageDelayed(obtainMessage(MESSAGE_SEARCH_PENDING, 0, 0, null),
+                    MESSAGE_SEARCH_PENDING_DELAY);
+        }
+
+        public void removeDelayedLoadMessage() {
+            removeMessages(MESSAGE_SEARCH_PENDING);
+        }
+    }
+
+    private final DelayedMessageHandler mDelayedMessageHandler = new DelayedMessageHandler();
+
+    private EntriesUpdatedObserver mEntriesUpdatedObserver;
+
+    /**
+     * Constructor for email queries.
+     */
+    public BaseRecipientAdapter(Context context) {
+        this(context, DEFAULT_PREFERRED_MAX_RESULT_COUNT, QUERY_TYPE_EMAIL);
+    }
+
+    public BaseRecipientAdapter(Context context, int preferredMaxResultCount) {
+        this(context, preferredMaxResultCount, QUERY_TYPE_EMAIL);
+    }
+
+    public BaseRecipientAdapter(int queryMode, Context context) {
+        this(context, DEFAULT_PREFERRED_MAX_RESULT_COUNT, queryMode);
+    }
+
+    public BaseRecipientAdapter(int queryMode, Context context, int preferredMaxResultCount) {
+        this(context, preferredMaxResultCount, queryMode);
+    }
+
+    public BaseRecipientAdapter(Context context, int preferredMaxResultCount, int queryMode) {
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+        mInflater = LayoutInflater.from(context);
+        mPreferredMaxResultCount = preferredMaxResultCount;
+        mPhotoCacheMap = new LruCache<Uri, byte[]>(PHOTO_CACHE_SIZE);
+        mQueryType = queryMode;
+
+        if (queryMode == QUERY_TYPE_EMAIL) {
+            mQuery = Queries.EMAIL;
+        } else if (queryMode == QUERY_TYPE_PHONE) {
+            mQuery = Queries.PHONE;
+        } else {
+            mQuery = Queries.EMAIL;
+            Log.e(TAG, "Unsupported query type: " + queryMode);
+        }
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    public int getQueryType() {
+        return mQueryType;
+    }
+
+    public void setDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter) {
+        mDropdownChipLayouter = dropdownChipLayouter;
+        mDropdownChipLayouter.setQuery(mQuery);
+    }
+
+    public DropdownChipLayouter getDropdownChipLayouter() {
+        return mDropdownChipLayouter;
+    }
+
+    /**
+     * Set the account when known. Causes the search to prioritize contacts from that account.
+     */
+    @Override
+    public void setAccount(Account account) {
+        mAccount = account;
+    }
+
+    /** Will be called from {@link AutoCompleteTextView} to prepare auto-complete list. */
+    @Override
+    public Filter getFilter() {
+        return new DefaultFilter();
+    }
+
+    /**
+     * An extesion to {@link RecipientAlternatesAdapter#getMatchingRecipients} that allows
+     * additional sources of contacts to be considered as matching recipients.
+     * @param addresses A set of addresses to be matched
+     * @return A list of matches or null if none found
+     */
+    public Map<String, RecipientEntry> getMatchingRecipients(Set<String> addresses) {
+        return null;
+    }
+
+    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()) {
+            final long id = directoryCursor.getLong(DirectoryListQuery.ID);
+
+            // Skip the local invisible directory, because the default directory already includes
+            // all local results.
+            if (id == Directory.LOCAL_INVISIBLE) {
+                continue;
+            }
+
+            final DirectorySearchParams params = new DirectorySearchParams();
+            final String packageName = directoryCursor.getString(DirectoryListQuery.PACKAGE_NAME);
+            final int resourceId = directoryCursor.getInt(DirectoryListQuery.TYPE_RESOURCE_ID);
+            params.directoryId = id;
+            params.displayName = directoryCursor.getString(DirectoryListQuery.DISPLAY_NAME);
+            params.accountName = directoryCursor.getString(DirectoryListQuery.ACCOUNT_NAME);
+            params.accountType = directoryCursor.getString(DirectoryListQuery.ACCOUNT_TYPE);
+            if (packageName != null && resourceId != 0) {
+                try {
+                    final Resources resources =
+                            packageManager.getResourcesForApplication(packageName);
+                    params.directoryType = resources.getString(resourceId);
+                    if (params.directoryType == null) {
+                        Log.e(TAG, "Cannot resolve directory name: "
+                                + resourceId + "@" + packageName);
+                    }
+                } catch (NameNotFoundException e) {
+                    Log.e(TAG, "Cannot resolve directory name: "
+                            + resourceId + "@" + packageName, e);
+                }
+            }
+
+            // 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 (account != null && account.name.equals(params.accountName) &&
+                    account.type.equals(params.accountType)) {
+                preferredDirectory = params;
+            } else {
+                paramsList.add(params);
+            }
+        }
+
+        if (preferredDirectory != null) {
+            paramsList.add(1, preferredDirectory);
+        }
+
+        return paramsList;
+    }
+
+    /**
+     * Starts search in other directories using {@link Filter}. Results will be handled in
+     * {@link DirectoryFilter}.
+     */
+    protected void startSearchOtherDirectories(
+            CharSequence constraint, List<DirectorySearchParams> paramsList, int limit) {
+        final int count = paramsList.size();
+        // Note: skipping the default partition (index 0), which has already been loaded
+        for (int i = 1; i < count; i++) {
+            final DirectorySearchParams params = paramsList.get(i);
+            params.constraint = constraint;
+            if (params.filter == null) {
+                params.filter = new DirectoryFilter(params);
+            }
+            params.filter.setLimit(limit);
+            params.filter.filter(constraint);
+        }
+
+        // Directory search started. We may show "waiting" message if directory results are slow
+        // enough.
+        mRemainingDirectoryCount = count - 1;
+        mDelayedMessageHandler.sendDelayedLoadMessage();
+    }
+
+    private static void putOneEntry(TemporaryEntry entry, boolean isAggregatedEntry,
+            LinkedHashMap<Long, List<RecipientEntry>> entryMap,
+            List<RecipientEntry> nonAggregatedEntries,
+            Set<String> existingDestinations) {
+        if (existingDestinations.contains(entry.destination)) {
+            return;
+        }
+
+        existingDestinations.add(entry.destination);
+
+        if (!isAggregatedEntry) {
+            nonAggregatedEntries.add(RecipientEntry.constructTopLevelEntry(
+                    entry.displayName,
+                    entry.displayNameSource,
+                    entry.destination, entry.destinationType, entry.destinationLabel,
+                    entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
+                    true, entry.lookupKey));
+        } else if (entryMap.containsKey(entry.contactId)) {
+            // We already have a section for the person.
+            final List<RecipientEntry> entryList = entryMap.get(entry.contactId);
+            entryList.add(RecipientEntry.constructSecondLevelEntry(
+                    entry.displayName,
+                    entry.displayNameSource,
+                    entry.destination, entry.destinationType, entry.destinationLabel,
+                    entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
+                    true, entry.lookupKey));
+        } 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.directoryId, entry.dataId, entry.thumbnailUriString,
+                    true, entry.lookupKey));
+            entryMap.put(entry.contactId, entryList);
+        }
+    }
+
+    /**
+     * Constructs an actual list for this Adapter using {@link #mEntryMap}. Also tries to
+     * fetch a cached photo for each contact entry (other than separators), or request another
+     * thread to get one from directories.
+     */
+    private List<RecipientEntry> constructEntryList(
+            LinkedHashMap<Long, List<RecipientEntry>> entryMap,
+            List<RecipientEntry> nonAggregatedEntries) {
+        final List<RecipientEntry> entries = new ArrayList<RecipientEntry>();
+        int validEntryCount = 0;
+        for (Map.Entry<Long, List<RecipientEntry>> mapEntry : entryMap.entrySet()) {
+            final List<RecipientEntry> entryList = mapEntry.getValue();
+            final int size = entryList.size();
+            for (int i = 0; i < size; i++) {
+                RecipientEntry entry = entryList.get(i);
+                entries.add(entry);
+                tryFetchPhoto(entry);
+                validEntryCount++;
+            }
+            if (validEntryCount > mPreferredMaxResultCount) {
+                break;
+            }
+        }
+        if (validEntryCount <= mPreferredMaxResultCount) {
+            for (RecipientEntry entry : nonAggregatedEntries) {
+                if (validEntryCount > mPreferredMaxResultCount) {
+                    break;
+                }
+                entries.add(entry);
+                tryFetchPhoto(entry);
+
+                validEntryCount++;
+            }
+        }
+
+        return entries;
+    }
+
+
+    public 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;
+    }
+
+    protected List<RecipientEntry> getEntries() {
+        return mTempEntries != null ? mTempEntries : mEntries;
+    }
+
+    private void tryFetchPhoto(final RecipientEntry entry) {
+        final Uri photoThumbnailUri = entry.getPhotoThumbnailUri();
+        if (photoThumbnailUri != null) {
+            final byte[] photoBytes = mPhotoCacheMap.get(photoThumbnailUri);
+            if (photoBytes != null) {
+                entry.setPhotoBytes(photoBytes);
+                // notifyDataSetChanged() should be called by a caller.
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, "No photo cache for " + entry.getDisplayName()
+                            + ". Fetch one asynchronously");
+                }
+                fetchPhotoAsync(entry, photoThumbnailUri);
+            }
+        }
+    }
+
+    // For reading photos for directory contacts, this is the chunksize for
+    // copying from the inputstream to the output stream.
+    private static final int BUFFER_SIZE = 1024*16;
+
+    private void fetchPhotoAsync(final RecipientEntry entry, final Uri photoThumbnailUri) {
+        final AsyncTask<Void, Void, byte[]> photoLoadTask = new AsyncTask<Void, Void, byte[]>() {
+            @Override
+            protected byte[] doInBackground(Void... params) {
+                // First try running a query. Images for local contacts are
+                // loaded by sending a query to the ContactsProvider.
+                final Cursor photoCursor = mContentResolver.query(
+                        photoThumbnailUri, PhotoQuery.PROJECTION, null, null, null);
+                if (photoCursor != null) {
+                    try {
+                        if (photoCursor.moveToFirst()) {
+                            return photoCursor.getBlob(PhotoQuery.PHOTO);
+                        }
+                    } finally {
+                        photoCursor.close();
+                    }
+                } else {
+                    // If the query fails, try streaming the URI directly.
+                    // For remote directory images, this URI resolves to the
+                    // directory provider and the images are loaded by sending
+                    // an openFile call to the provider.
+                    try {
+                        InputStream is = mContentResolver.openInputStream(
+                                photoThumbnailUri);
+                        if (is != null) {
+                            byte[] buffer = new byte[BUFFER_SIZE];
+                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                            try {
+                                int size;
+                                while ((size = is.read(buffer)) != -1) {
+                                    baos.write(buffer, 0, size);
+                                }
+                            } finally {
+                                is.close();
+                            }
+                            return baos.toByteArray();
+                        }
+                    } catch (IOException ex) {
+                        // ignore
+                    }
+                }
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(final byte[] photoBytes) {
+                entry.setPhotoBytes(photoBytes);
+                if (photoBytes != null) {
+                    mPhotoCacheMap.put(photoThumbnailUri, photoBytes);
+                    notifyDataSetChanged();
+                }
+            }
+        };
+        photoLoadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
+    }
+
+    protected void fetchPhoto(final RecipientEntry entry, final Uri photoThumbnailUri) {
+        byte[] photoBytes = mPhotoCacheMap.get(photoThumbnailUri);
+        if (photoBytes != null) {
+            entry.setPhotoBytes(photoBytes);
+            return;
+        }
+        final Cursor photoCursor = mContentResolver.query(photoThumbnailUri, PhotoQuery.PROJECTION,
+                null, null, null);
+        if (photoCursor != null) {
+            try {
+                if (photoCursor.moveToFirst()) {
+                    photoBytes = photoCursor.getBlob(PhotoQuery.PHOTO);
+                    entry.setPhotoBytes(photoBytes);
+                    mPhotoCacheMap.put(photoThumbnailUri, photoBytes);
+                }
+            } finally {
+                photoCursor.close();
+            }
+        } else {
+            InputStream inputStream = null;
+            ByteArrayOutputStream outputStream = null;
+            try {
+                inputStream = mContentResolver.openInputStream(photoThumbnailUri);
+                final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
+
+                if (bitmap != null) {
+                    outputStream = new ByteArrayOutputStream();
+                    bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
+                    photoBytes = outputStream.toByteArray();
+
+                    entry.setPhotoBytes(photoBytes);
+                    mPhotoCacheMap.put(photoThumbnailUri, photoBytes);
+                }
+            } catch (final FileNotFoundException e) {
+                Log.w(TAG, "Error opening InputStream for photo", e);
+            } finally {
+                try {
+                    if (inputStream != null) {
+                        inputStream.close();
+                    }
+                } catch (IOException e) {
+                    Log.e(TAG, "Error closing photo input stream", e);
+                }
+                try {
+                    if (outputStream != null) {
+                        outputStream.close();
+                    }
+                } catch (IOException e) {
+                    Log.e(TAG, "Error closing photo output stream", e);
+                }
+            }
+        }
+    }
+
+    private Cursor doQuery(CharSequence constraint, int limit, Long directoryId) {
+        final Uri.Builder builder = mQuery.getContentFilterUri().buildUpon()
+                .appendPath(constraint.toString())
+                .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
+                        String.valueOf(limit + ALLOWANCE_FOR_DUPLICATES));
+        if (directoryId != null) {
+            builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+                    String.valueOf(directoryId));
+        }
+        if (mAccount != null) {
+            builder.appendQueryParameter(PRIMARY_ACCOUNT_NAME, mAccount.name);
+            builder.appendQueryParameter(PRIMARY_ACCOUNT_TYPE, mAccount.type);
+        }
+        final long start = System.currentTimeMillis();
+        final Cursor cursor = mContentResolver.query(
+                builder.build(), mQuery.getProjection(), null, null, null);
+        final long end = System.currentTimeMillis();
+        if (DEBUG) {
+            Log.d(TAG, "Time for autocomplete (query: " + constraint
+                    + ", directoryId: " + directoryId + ", num_of_results: "
+                    + (cursor != null ? cursor.getCount() : "null") + "): "
+                    + (end - start) + " ms");
+        }
+        return cursor;
+    }
+
+    // TODO: This won't be used at all. We should find better way to quit the thread..
+    /*public void close() {
+        mEntries = null;
+        mPhotoCacheMap.evictAll();
+        if (!sPhotoHandlerThread.quit()) {
+            Log.w(TAG, "Failed to quit photo handler thread, ignoring it.");
+        }
+    }*/
+
+    @Override
+    public int getCount() {
+        final List<RecipientEntry> entries = getEntries();
+        return entries != null ? entries.size() : 0;
+    }
+
+    @Override
+    public RecipientEntry getItem(int position) {
+        return getEntries().get(position);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        return RecipientEntry.ENTRY_TYPE_SIZE;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return getEntries().get(position).getEntryType();
+    }
+
+    @Override
+    public boolean isEnabled(int position) {
+        return getEntries().get(position).isSelectable();
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        final RecipientEntry entry = getEntries().get(position);
+
+        final String constraint = mCurrentConstraint == null ? null :
+                mCurrentConstraint.toString();
+
+        return mDropdownChipLayouter.bindView(convertView, parent, entry, position,
+                AdapterType.BASE_RECIPIENT, constraint);
+    }
+
+    public Account getAccount() {
+        return mAccount;
+    }
+}
diff --git a/src/com/android/ex/chips/ChipsUtil.java b/src/com/android/ex/chips/ChipsUtil.java
new file mode 100644
index 0000000..559b2c9
--- /dev/null
+++ b/src/com/android/ex/chips/ChipsUtil.java
@@ -0,0 +1,29 @@
+/*
+ * 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.os.Build;
+
+public class ChipsUtil {
+
+    /**
+     * @return true when the caller can use Chips UI in its environment.
+     */
+    public static boolean supportsChipsUi() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/ex/chips/DropdownChipLayouter.java b/src/com/android/ex/chips/DropdownChipLayouter.java
new file mode 100644
index 0000000..6b0e78e
--- /dev/null
+++ b/src/com/android/ex/chips/DropdownChipLayouter.java
@@ -0,0 +1,274 @@
+package com.android.ex.chips;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.text.util.Rfc822Tokenizer;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.ex.chips.Queries.Query;
+
+/**
+ * A class that inflates and binds the views in the dropdown list from
+ * RecipientEditTextView.
+ */
+public class DropdownChipLayouter {
+    /**
+     * The type of adapter that is requesting a chip layout.
+     */
+    public enum AdapterType {
+        BASE_RECIPIENT,
+        RECIPIENT_ALTERNATES,
+        SINGLE_RECIPIENT
+    }
+
+    private final LayoutInflater mInflater;
+    private final Context mContext;
+    private Query mQuery;
+
+    public DropdownChipLayouter(LayoutInflater inflater, Context context) {
+        mInflater = inflater;
+        mContext = context;
+    }
+
+    public void setQuery(Query query) {
+        mQuery = query;
+    }
+
+
+    /**
+     * Layouts and binds recipient information to the view. If convertView is null, inflates a new
+     * view with getItemLaytout().
+     *
+     * @param convertView The view to bind information to.
+     * @param parent The parent to bind the view to if we inflate a new view.
+     * @param entry The recipient entry to get information from.
+     * @param position The position in the list.
+     * @param type The adapter type that is requesting the bind.
+     * @param constraint The constraint typed in the auto complete view.
+     *
+     * @return A view ready to be shown in the drop down list.
+     */
+    public View bindView(View convertView, ViewGroup parent, RecipientEntry entry, int position,
+        AdapterType type, String constraint) {
+        // Default to show all the information
+        String displayName = entry.getDisplayName();
+        String destination = entry.getDestination();
+        boolean showImage = true;
+        CharSequence destinationType = getDestinationType(entry);
+
+        final View itemView = reuseOrInflateView(convertView, parent, type);
+
+        final ViewHolder viewHolder = new ViewHolder(itemView);
+
+        // Hide some information depending on the entry type and adapter type
+        switch (type) {
+            case BASE_RECIPIENT:
+                if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, destination)) {
+                    displayName = destination;
+
+                    // We only show the destination for secondary entries, so clear it only for the
+                    // first level.
+                    if (entry.isFirstLevel()) {
+                        destination = null;
+                    }
+                }
+
+                if (!entry.isFirstLevel()) {
+                    displayName = null;
+                    showImage = false;
+                }
+                break;
+            case RECIPIENT_ALTERNATES:
+                if (position != 0) {
+                    displayName = null;
+                    showImage = false;
+                }
+                break;
+            case SINGLE_RECIPIENT:
+                destination = Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress();
+                destinationType = null;
+        }
+
+        // Bind the information to the view
+        bindTextToView(displayName, viewHolder.displayNameView);
+        bindTextToView(destination, viewHolder.destinationView);
+        bindTextToView(destinationType, viewHolder.destinationTypeView);
+        bindIconToView(showImage, entry, viewHolder.imageView, type);
+
+        return itemView;
+    }
+
+    /**
+     * Returns a new view with {@link #getItemLayoutResId()}.
+     */
+    public View newView() {
+        return mInflater.inflate(getItemLayoutResId(), null);
+    }
+
+    /**
+     * Returns the same view, or inflates a new one if the given view was null.
+     */
+    protected View reuseOrInflateView(View convertView, ViewGroup parent, AdapterType type) {
+        int itemLayout = getItemLayoutResId();
+        switch (type) {
+            case BASE_RECIPIENT:
+            case RECIPIENT_ALTERNATES:
+                break;
+            case SINGLE_RECIPIENT:
+                itemLayout = getAlternateItemLayoutResId();
+                break;
+        }
+        return convertView != null ? convertView : mInflater.inflate(itemLayout, parent, false);
+    }
+
+    /**
+     * Binds the text to the given text view. If the text was null, hides the text view.
+     */
+    protected void bindTextToView(CharSequence text, TextView view) {
+        if (view == null) {
+            return;
+        }
+
+        if (text != null) {
+            view.setText(text);
+            view.setVisibility(View.VISIBLE);
+        } else {
+            view.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Binds the avatar icon to the image view. If we don't want to show the image, hides the
+     * image view.
+     */
+    protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view,
+        AdapterType type) {
+        if (view == null) {
+            return;
+        }
+
+        if (showImage) {
+            switch (type) {
+                case BASE_RECIPIENT:
+                    byte[] photoBytes = entry.getPhotoBytes();
+                    if (photoBytes != null && photoBytes.length > 0) {
+                        final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
+                            photoBytes.length);
+                        view.setImageBitmap(photo);
+                    } else {
+                        view.setImageResource(getDefaultPhotoResId());
+                    }
+                    break;
+                case RECIPIENT_ALTERNATES:
+                    Uri thumbnailUri = entry.getPhotoThumbnailUri();
+                    if (thumbnailUri != null) {
+                        // TODO: see if this needs to be done outside the main thread
+                        // as it may be too slow to get immediately.
+                        view.setImageURI(thumbnailUri);
+                    } else {
+                        view.setImageResource(getDefaultPhotoResId());
+                    }
+                    break;
+                case SINGLE_RECIPIENT:
+                default:
+                    break;
+            }
+            view.setVisibility(View.VISIBLE);
+        } else {
+            view.setVisibility(View.GONE);
+        }
+    }
+
+    protected CharSequence getDestinationType(RecipientEntry entry) {
+        return mQuery.getTypeLabel(mContext.getResources(), entry.getDestinationType(),
+            entry.getDestinationLabel()).toString().toUpperCase();
+    }
+
+    /**
+     * Returns a layout id for each item inside auto-complete list.
+     *
+     * Each View must contain two TextViews (for display name and destination) and one ImageView
+     * (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
+     * {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
+     */
+    protected int getItemLayoutResId() {
+        return R.layout.chips_recipient_dropdown_item;
+    }
+
+    /**
+     * Returns a layout id for each item inside alternate auto-complete list.
+     *
+     * Each View must contain two TextViews (for display name and destination) and one ImageView
+     * (for photo). Ids for those should be available via {@link #getDisplayNameResId()},
+     * {@link #getDestinationResId()}, and {@link #getPhotoResId()}.
+     */
+    protected int getAlternateItemLayoutResId() {
+        return R.layout.chips_alternate_item;
+    }
+
+    /**
+     * Returns a resource ID representing an image which should be shown when ther's no relevant
+     * photo is available.
+     */
+    protected int getDefaultPhotoResId() {
+        return R.drawable.ic_contact_picture;
+    }
+
+    /**
+     * Returns an id for TextView in an item View for showing a display name. By default
+     * {@link android.R.id#title} is returned.
+     */
+    protected int getDisplayNameResId() {
+        return android.R.id.title;
+    }
+
+    /**
+     * Returns an id for TextView in an item View for showing a destination
+     * (an email address or a phone number).
+     * By default {@link android.R.id#text1} is returned.
+     */
+    protected int getDestinationResId() {
+        return android.R.id.text1;
+    }
+
+    /**
+     * Returns an id for TextView in an item View for showing the type of the destination.
+     * By default {@link android.R.id#text2} is returned.
+     */
+    protected int getDestinationTypeResId() {
+        return android.R.id.text2;
+    }
+
+    /**
+     * Returns an id for ImageView in an item View for showing photo image for a person. In default
+     * {@link android.R.id#icon} is returned.
+     */
+    protected int getPhotoResId() {
+        return android.R.id.icon;
+    }
+
+    /**
+     * A holder class the view. Uses the getters in DropdownChipLayouter to find the id of the
+     * corresponding views.
+     */
+    protected class ViewHolder {
+        public final TextView displayNameView;
+        public final TextView destinationView;
+        public final TextView destinationTypeView;
+        public final ImageView imageView;
+
+        public ViewHolder(View view) {
+            displayNameView = (TextView) view.findViewById(getDisplayNameResId());
+            destinationView = (TextView) view.findViewById(getDestinationResId());
+            destinationTypeView = (TextView) view.findViewById(getDestinationTypeResId());
+            imageView = (ImageView) view.findViewById(getPhotoResId());
+        }
+    }
+}
diff --git a/src/com/android/ex/chips/Queries.java b/src/com/android/ex/chips/Queries.java
new file mode 100644
index 0000000..1e66b96
--- /dev/null
+++ b/src/com/android/ex/chips/Queries.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2012 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.content.res.Resources;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+
+/**
+ * Phone and Email queries for supporting Chips UI.
+ */
+/* package */ class Queries {
+
+    public static final Query PHONE = new Query(new String[] {
+            Contacts.DISPLAY_NAME,                          // 0
+            Phone.NUMBER,                                   // 1
+            Phone.TYPE,                                     // 2
+            Phone.LABEL,                                    // 3
+            Phone.CONTACT_ID,                               // 4
+            Phone._ID,                                      // 5
+            Contacts.PHOTO_THUMBNAIL_URI,                   // 6
+            Contacts.DISPLAY_NAME_SOURCE,                   // 7
+            Contacts.LOOKUP_KEY,                            // 8
+            ContactsContract.CommonDataKinds.Email.MIMETYPE // 9
+        }, Phone.CONTENT_FILTER_URI, Phone.CONTENT_URI) {
+
+            @Override
+            public CharSequence getTypeLabel(Resources res, int type, CharSequence label) {
+                return Phone.getTypeLabel(res, type, label);
+            }
+
+    };
+
+    public static final Query EMAIL = new Query(new String[]{
+            Contacts.DISPLAY_NAME,                          // 0
+            Email.DATA,                                     // 1
+            Email.TYPE,                                     // 2
+            Email.LABEL,                                    // 3
+            Email.CONTACT_ID,                               // 4
+            Email._ID,                                      // 5
+            Contacts.PHOTO_THUMBNAIL_URI,                   // 6
+            Contacts.DISPLAY_NAME_SOURCE,                   // 7
+            Contacts.LOOKUP_KEY,                            // 8
+            ContactsContract.CommonDataKinds.Email.MIMETYPE // 9
+        }, Email.CONTENT_FILTER_URI, Email.CONTENT_URI) {
+
+            @Override
+            public CharSequence getTypeLabel(Resources res, int type, CharSequence label) {
+                return Email.getTypeLabel(res, type, label);
+            }
+
+    };
+
+    static abstract class Query {
+        private final String[] mProjection;
+        private final Uri mContentFilterUri;
+        private final Uri mContentUri;
+
+        public static final int NAME = 0;                // String
+        public static final int DESTINATION = 1;         // String
+        public static final int DESTINATION_TYPE = 2;    // int
+        public static final int DESTINATION_LABEL = 3;   // String
+        public static final int CONTACT_ID = 4;          // long
+        public static final int DATA_ID = 5;             // long
+        public static final int PHOTO_THUMBNAIL_URI = 6; // String
+        public static final int DISPLAY_NAME_SOURCE = 7; // int
+        public static final int LOOKUP_KEY = 8;          // String
+        public static final int MIME_TYPE = 9;           // String
+
+        public Query(String[] projection, Uri contentFilter, Uri content) {
+            mProjection = projection;
+            mContentFilterUri = contentFilter;
+            mContentUri = content;
+        }
+
+        public String[] getProjection() {
+            return mProjection;
+        }
+
+        public Uri getContentFilterUri() {
+            return mContentFilterUri;
+        }
+
+        public Uri getContentUri() {
+            return mContentUri;
+        }
+
+        public abstract CharSequence getTypeLabel(Resources res, int type, CharSequence label);
+    }
+}
diff --git a/src/com/android/ex/chips/RecipientAlternatesAdapter.java b/src/com/android/ex/chips/RecipientAlternatesAdapter.java
new file mode 100644
index 0000000..f6f662d
--- /dev/null
+++ b/src/com/android/ex/chips/RecipientAlternatesAdapter.java
@@ -0,0 +1,576 @@
+/*
+ * 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.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.provider.ContactsContract.Contacts;
+import android.text.TextUtils;
+import android.text.util.Rfc822Token;
+import android.text.util.Rfc822Tokenizer;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CursorAdapter;
+
+import com.android.ex.chips.BaseRecipientAdapter.DirectoryListQuery;
+import com.android.ex.chips.BaseRecipientAdapter.DirectorySearchParams;
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
+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
+ * queried by email or by phone number.
+ */
+public class RecipientAlternatesAdapter extends CursorAdapter {
+    static final int MAX_LOOKUPS = 50;
+
+    private final long mCurrentId;
+
+    private int mCheckedItemPosition = -1;
+
+    private OnCheckedItemChangedListener mCheckedItemChangedListener;
+
+    private static final String TAG = "RecipAlternates";
+
+    public static final int QUERY_TYPE_EMAIL = 0;
+    public static final int QUERY_TYPE_PHONE = 1;
+    private final Long mDirectoryId;
+    private DropdownChipLayouter mDropdownChipLayouter;
+
+    private static final Map<String, String> sCorrectedPhotoUris = new HashMap<String, String>();
+
+    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, BaseRecipientAdapter adapter,
+            ArrayList<String> inAddresses, Account account, RecipientMatchCallback callback) {
+        getMatchingRecipients(context, adapter, inAddresses, QUERY_TYPE_EMAIL, account, callback);
+    }
+
+    /**
+     * Get a HashMap of address to RecipientEntry that contains all contact
+     * information for a contact with the provided address, if one exists. This
+     * may block the UI, so run it in an async task.
+     *
+     * @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 void getMatchingRecipients(Context context, BaseRecipientAdapter adapter,
+            ArrayList<String> inAddresses, int addressType, Account account,
+            RecipientMatchCallback callback) {
+        Queries.Query query;
+        if (addressType == QUERY_TYPE_EMAIL) {
+            query = Queries.EMAIL;
+        } else {
+            query = Queries.PHONE;
+        }
+        int addressesSize = Math.min(MAX_LOOKUPS, inAddresses.size());
+        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.add(tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i));
+            bindString.append("?");
+            if (i < addressesSize - 1) {
+                bindString.append(",");
+            }
+        }
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Doing reverse lookup for " + addresses.toString());
+        }
+
+        String[] addressArray = new String[addresses.size()];
+        addresses.toArray(addressArray);
+        HashMap<String, RecipientEntry> recipientEntries = null;
+        Cursor c = null;
+
+        try {
+            c = context.getContentResolver().query(
+                    query.getContentUri(),
+                    query.getProjection(),
+                    query.getProjection()[Queries.Query.DESTINATION] + " IN ("
+                            + bindString.toString() + ")", addressArray, null);
+            recipientEntries = processContactEntries(c, null /* directoryId */);
+            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);
+                if (directoryCursor == null) {
+                    paramsList = null;
+                } else {
+                    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);
+
+            if (paramsList != null) {
+                Cursor directoryContactsCursor = null;
+                for (String unresolvedAddress : unresolvedAddresses) {
+                    Long directoryId = null;
+                    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 {
+                                directoryId = paramsList.get(i).directoryId;
+                                break;
+                            }
+                        }
+                    }
+                    if (directoryContactsCursor != null) {
+                        try {
+                            final Map<String, RecipientEntry> entries =
+                                    processContactEntries(directoryContactsCursor, directoryId);
+
+                            for (final String address : entries.keySet()) {
+                                matchesNotFound.remove(address);
+                            }
+
+                            callback.matchesFound(entries);
+                        } finally {
+                            directoryContactsCursor.close();
+                        }
+                    }
+                }
+            }
+        }
+
+        // If no matches found in contact provider or the directories, try the extension
+        // matcher.
+        // todo (aalbert): This whole method needs to be in the adapter?
+        if (adapter != null) {
+            final Map<String, RecipientEntry> entries =
+                    adapter.getMatchingRecipients(matchesNotFound);
+            if (entries != null && entries.size() > 0) {
+                callback.matchesFound(entries);
+                for (final String address : entries.keySet()) {
+                    matchesNotFound.remove(address);
+                }
+            }
+        }
+        callback.matchesNotFound(matchesNotFound);
+    }
+
+    private static HashMap<String, RecipientEntry> processContactEntries(Cursor c,
+            Long directoryId) {
+        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),
+                        directoryId,
+                        c.getLong(Queries.Query.DATA_ID),
+                        c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
+                        true,
+                        c.getString(Queries.Query.LOOKUP_KEY));
+
+                /*
+                 * 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;
+    }
+
+    /**
+     * 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;
+    }
+
+    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 directoryId,
+            String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener,
+            DropdownChipLayouter dropdownChipLayouter) {
+        super(context,
+                getCursorForConstruction(context, contactId, directoryId, lookupKey, queryMode), 0);
+        mCurrentId = currentId;
+        mDirectoryId = directoryId;
+        mCheckedItemChangedListener = listener;
+
+        mDropdownChipLayouter = dropdownChipLayouter;
+    }
+
+    private static Cursor getCursorForConstruction(Context context, long contactId,
+            Long directoryId, String lookupKey, int queryType) {
+        final Cursor cursor;
+        final String desiredMimeType;
+        if (queryType == QUERY_TYPE_EMAIL) {
+            final Uri uri;
+            final StringBuilder selection = new StringBuilder();
+            selection.append(Queries.EMAIL.getProjection()[Queries.Query.CONTACT_ID]);
+            selection.append(" = ?");
+
+            if (directoryId == null || lookupKey == null) {
+                uri = Queries.EMAIL.getContentUri();
+                desiredMimeType = null;
+            } else {
+                final Uri.Builder builder = Contacts.getLookupUri(contactId, lookupKey).buildUpon();
+                builder.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
+                        .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+                                String.valueOf(directoryId));
+                uri = builder.build();
+                desiredMimeType = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
+            }
+            cursor = context.getContentResolver().query(
+                    uri,
+                    Queries.EMAIL.getProjection(),
+                    selection.toString(), new String[] {
+                        String.valueOf(contactId)
+                    }, null);
+        } else {
+            final Uri uri;
+            final StringBuilder selection = new StringBuilder();
+            selection.append(Queries.PHONE.getProjection()[Queries.Query.CONTACT_ID]);
+            selection.append(" = ?");
+
+            if (lookupKey == null) {
+                uri = Queries.PHONE.getContentUri();
+                desiredMimeType = null;
+            } else {
+                final Uri.Builder builder = Contacts.getLookupUri(contactId, lookupKey).buildUpon();
+                builder.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
+                        .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+                                String.valueOf(directoryId));
+                uri = builder.build();
+                desiredMimeType = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;
+            }
+            cursor = context.getContentResolver().query(
+                    uri,
+                    Queries.PHONE.getProjection(),
+                    selection.toString(), new String[] {
+                        String.valueOf(contactId)
+                    }, null);
+        }
+
+        final Cursor resultCursor = removeUndesiredDestinations(cursor, desiredMimeType, lookupKey);
+        cursor.close();
+
+        return resultCursor;
+    }
+
+    /**
+     * @return a new cursor based on the given cursor with all duplicate destinations removed.
+     *
+     * It's only intended to use for the alternate list, so...
+     * - This method ignores all other fields and dedupe solely on the destination.  Normally,
+     * if a cursor contains multiple contacts and they have the same destination, we'd still want
+     * to show both.
+     * - This method creates a MatrixCursor, so all data will be kept in memory.  We wouldn't want
+     * to do this if the original cursor is large, but it's okay here because the alternate list
+     * won't be that big.
+     *
+     * @param desiredMimeType If this is non-<code>null</code>, only entries with this mime type
+     *            will be added to the cursor
+     * @param lookupKey The lookup key used for this contact if there isn't one in the cursor. This
+     *            should be the same one used in the query that returned the cursor
+     */
+    // Visible for testing
+    static Cursor removeUndesiredDestinations(final Cursor original, final String desiredMimeType,
+            final String lookupKey) {
+        final MatrixCursor result = new MatrixCursor(
+                original.getColumnNames(), original.getCount());
+        final HashSet<String> destinationsSeen = new HashSet<String>();
+
+        String defaultDisplayName = null;
+        String defaultPhotoThumbnailUri = null;
+        int defaultDisplayNameSource = 0;
+
+        // Find some nice defaults in case we need them
+        original.moveToPosition(-1);
+        while (original.moveToNext()) {
+            final String mimeType = original.getString(Query.MIME_TYPE);
+
+            if (ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE.equals(
+                    mimeType)) {
+                // Store this data
+                defaultDisplayName = original.getString(Query.NAME);
+                defaultPhotoThumbnailUri = original.getString(Query.PHOTO_THUMBNAIL_URI);
+                defaultDisplayNameSource = original.getInt(Query.DISPLAY_NAME_SOURCE);
+                break;
+            }
+        }
+
+        original.moveToPosition(-1);
+        while (original.moveToNext()) {
+            if (desiredMimeType != null) {
+                final String mimeType = original.getString(Query.MIME_TYPE);
+                if (!desiredMimeType.equals(mimeType)) {
+                    continue;
+                }
+            }
+            final String destination = original.getString(Query.DESTINATION);
+            if (destinationsSeen.contains(destination)) {
+                continue;
+            }
+            destinationsSeen.add(destination);
+
+            final Object[] row = new Object[] {
+                    original.getString(Query.NAME),
+                    original.getString(Query.DESTINATION),
+                    original.getInt(Query.DESTINATION_TYPE),
+                    original.getString(Query.DESTINATION_LABEL),
+                    original.getLong(Query.CONTACT_ID),
+                    original.getLong(Query.DATA_ID),
+                    original.getString(Query.PHOTO_THUMBNAIL_URI),
+                    original.getInt(Query.DISPLAY_NAME_SOURCE),
+                    original.getString(Query.LOOKUP_KEY),
+                    original.getString(Query.MIME_TYPE)
+            };
+
+            if (row[Query.NAME] == null) {
+                row[Query.NAME] = defaultDisplayName;
+            }
+            if (row[Query.PHOTO_THUMBNAIL_URI] == null) {
+                row[Query.PHOTO_THUMBNAIL_URI] = defaultPhotoThumbnailUri;
+            }
+            if ((Integer) row[Query.DISPLAY_NAME_SOURCE] == 0) {
+                row[Query.DISPLAY_NAME_SOURCE] = defaultDisplayNameSource;
+            }
+            if (row[Query.LOOKUP_KEY] == null) {
+                row[Query.LOOKUP_KEY] = lookupKey;
+            }
+
+            // Ensure we don't have two '?' like content://.../...?account_name=...?sz=...
+            final String photoThumbnailUri = (String) row[Query.PHOTO_THUMBNAIL_URI];
+            if (photoThumbnailUri != null) {
+                if (sCorrectedPhotoUris.containsKey(photoThumbnailUri)) {
+                    row[Query.PHOTO_THUMBNAIL_URI] = sCorrectedPhotoUris.get(photoThumbnailUri);
+                } else if (photoThumbnailUri.indexOf('?') != photoThumbnailUri.lastIndexOf('?')) {
+                    final String[] parts = photoThumbnailUri.split("\\?");
+                    final StringBuilder correctedUriBuilder = new StringBuilder();
+                    for (int i = 0; i < parts.length; i++) {
+                        if (i == 1) {
+                            correctedUriBuilder.append("?"); // We only want one of these
+                        } else if (i > 1) {
+                            correctedUriBuilder.append("&"); // And we want these elsewhere
+                        }
+                        correctedUriBuilder.append(parts[i]);
+                    }
+
+                    final String correctedUri = correctedUriBuilder.toString();
+                    sCorrectedPhotoUris.put(photoThumbnailUri, correctedUri);
+                    row[Query.PHOTO_THUMBNAIL_URI] = correctedUri;
+                }
+            }
+
+            result.addRow(row);
+        }
+
+        return result;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        Cursor c = getCursor();
+        if (c.moveToPosition(position)) {
+            c.getLong(Queries.Query.DATA_ID);
+        }
+        return -1;
+    }
+
+    public RecipientEntry getRecipientEntry(int position) {
+        Cursor c = getCursor();
+        c.moveToPosition(position);
+        return 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),
+                mDirectoryId,
+                c.getLong(Queries.Query.DATA_ID),
+                c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
+                true,
+                c.getString(Queries.Query.LOOKUP_KEY));
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        Cursor cursor = getCursor();
+        cursor.moveToPosition(position);
+        if (convertView == null) {
+            convertView = mDropdownChipLayouter.newView();
+        }
+        if (cursor.getLong(Queries.Query.DATA_ID) == mCurrentId) {
+            mCheckedItemPosition = position;
+            if (mCheckedItemChangedListener != null) {
+                mCheckedItemChangedListener.onCheckedItemChanged(mCheckedItemPosition);
+            }
+        }
+        bindView(convertView, convertView.getContext(), cursor);
+        return convertView;
+    }
+
+    @Override
+    public void bindView(View view, Context context, Cursor cursor) {
+        int position = cursor.getPosition();
+        RecipientEntry entry = getRecipientEntry(position);
+
+        mDropdownChipLayouter.bindView(view, null, entry, position,
+                AdapterType.RECIPIENT_ALTERNATES, null);
+    }
+
+    @Override
+    public View newView(Context context, Cursor cursor, ViewGroup parent) {
+        return mDropdownChipLayouter.newView();
+    }
+
+    /*package*/ static interface OnCheckedItemChangedListener {
+        public void onCheckedItemChanged(int position);
+    }
+}
diff --git a/src/com/android/ex/chips/RecipientEditTextView.java b/src/com/android/ex/chips/RecipientEditTextView.java
new file mode 100644
index 0000000..4339b9e
--- /dev/null
+++ b/src/com/android/ex/chips/RecipientEditTextView.java
@@ -0,0 +1,2988 @@
+/*
+
+ * 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.app.Dialog;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcelable;
+import android.text.Editable;
+import android.text.InputType;
+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;
+import android.text.TextWatcher;
+import android.text.method.QwertyKeyListener;
+import android.text.style.ImageSpan;
+import android.text.util.Rfc822Token;
+import android.text.util.Rfc822Tokenizer;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ActionMode;
+import android.view.ActionMode.Callback;
+import android.view.DragEvent;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewParent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+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;
+import android.widget.MultiAutoCompleteTextView;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+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;
+
+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;
+
+/**
+ * RecipientEditTextView is an auto complete text view for use with applications
+ * that use the new Chips UI for addressing a message to recipients.
+ */
+public class RecipientEditTextView extends MultiAutoCompleteTextView implements
+        OnItemClickListener, Callback, RecipientAlternatesAdapter.OnCheckedItemChangedListener,
+        GestureDetector.OnGestureListener, OnDismissListener, OnClickListener,
+        TextView.OnEditorActionListener {
+
+    private static final char COMMIT_CHAR_COMMA = ',';
+
+    private static final char COMMIT_CHAR_SEMICOLON = ';';
+
+    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();
+
+    private static final long DISMISS_DELAY = 300;
+
+    // TODO: get correct number/ algorithm from with UX.
+    // Visible for testing.
+    /*package*/ static final int CHIP_LIMIT = 2;
+
+    private static final int MAX_CHIPS_PARSED = 50;
+
+    private static int sSelectedTextColor = -1;
+
+    // Resources for displaying chips.
+    private Drawable mChipBackground = null;
+
+    private Drawable mChipDelete = null;
+
+    private Drawable mInvalidChipBackground;
+
+    private Drawable mChipBackgroundPressed;
+
+    private float mChipHeight;
+
+    private float mChipFontSize;
+
+    private float mLineSpacingExtra;
+
+    private int mChipPadding;
+
+    /**
+     * Enumerator for avatar position. See attr.xml for more details.
+     * 0 for end, 1 for start.
+     */
+    private int mAvatarPosition;
+
+    private static final int AVATAR_POSITION_END = 0;
+
+    private static final int AVATAR_POSITION_START = 1;
+
+    /**
+     * Enumerator for image span alignment. See attr.xml for more details.
+     * 0 for bottom, 1 for baseline.
+     */
+    private int mImageSpanAlignment;
+
+    private static final int IMAGE_SPAN_ALIGNMENT_BOTTOM = 0;
+
+    private static final int IMAGE_SPAN_ALIGNMENT_BASELINE = 1;
+
+
+    private boolean mDisableDelete;
+
+    private Tokenizer mTokenizer;
+
+    private Validator mValidator;
+
+    private DrawableRecipientChip mSelectedChip;
+
+    private Bitmap mDefaultContactPhoto;
+
+    private ImageSpan mMoreChip;
+
+    private TextView mMoreItem;
+
+    // VisibleForTesting
+    final ArrayList<String> mPendingChips = new ArrayList<String>();
+
+    private Handler mHandler;
+
+    private int mPendingChipsCount = 0;
+
+    private boolean mNoChips = false;
+
+    private ListPopupWindow mAlternatesPopup;
+
+    private ListPopupWindow mAddressPopup;
+
+    // VisibleForTesting
+    ArrayList<DrawableRecipientChip> mTemporaryRecipients;
+
+    private ArrayList<DrawableRecipientChip> mRemovedSpans;
+
+    private boolean mShouldShrink = true;
+
+    // Chip copy fields.
+    private GestureDetector mGestureDetector;
+
+    private Dialog mCopyDialog;
+
+    private String mCopyAddress;
+
+    /**
+     * Used with {@link #mAlternatesPopup}. Handles clicks to alternate addresses for a
+     * selected chip.
+     */
+    private OnItemClickListener mAlternatesListener;
+
+    private int mCheckedItem;
+
+    private TextWatcher mTextWatcher;
+
+    // Obtain the enclosing scroll view, if it exists, so that the view can be
+    // scrolled to show the last line of chips content.
+    private ScrollView mScrollView;
+
+    private boolean mTriedGettingScrollView;
+
+    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() {
+            if (mTextWatcher == null) {
+                mTextWatcher = new RecipientTextWatcher();
+                addTextChangedListener(mTextWatcher);
+            }
+        }
+    };
+
+    private IndividualReplacementTask mIndividualReplacements;
+
+    private Runnable mHandlePendingChips = new Runnable() {
+
+        @Override
+        public void run() {
+            handlePendingChips();
+        }
+
+    };
+
+    private Runnable mDelayedShrink = new Runnable() {
+
+        @Override
+        public void run() {
+            shrink();
+        }
+
+    };
+
+    private int mMaxLines;
+
+    private static int sExcessTopPadding = -1;
+
+    private int mActionBarHeight;
+
+    private boolean mAttachedToWindow;
+
+    private DropdownChipLayouter mDropdownChipLayouter;
+
+    public RecipientEditTextView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setChipDimensions(context, attrs);
+        if (sSelectedTextColor == -1) {
+            sSelectedTextColor = context.getResources().getColor(android.R.color.white);
+        }
+        mAlternatesPopup = new ListPopupWindow(context);
+        mAddressPopup = new ListPopupWindow(context);
+        mCopyDialog = new Dialog(context);
+        mAlternatesListener = new OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> adapterView,View view, int position,
+                    long rowId) {
+                mAlternatesPopup.setOnItemClickListener(null);
+                replaceChip(mSelectedChip, ((RecipientAlternatesAdapter) adapterView.getAdapter())
+                        .getRecipientEntry(position));
+                Message delayed = Message.obtain(mHandler, DISMISS);
+                delayed.obj = mAlternatesPopup;
+                mHandler.sendMessageDelayed(delayed, DISMISS_DELAY);
+                clearComposingText();
+            }
+        };
+        setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
+        setOnItemClickListener(this);
+        setCustomSelectionActionModeCallback(this);
+        mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == DISMISS) {
+                    ((ListPopupWindow) msg.obj).dismiss();
+                    return;
+                }
+                super.handleMessage(msg);
+            }
+        };
+        mTextWatcher = new RecipientTextWatcher();
+        addTextChangedListener(mTextWatcher);
+        mGestureDetector = new GestureDetector(context, this);
+        setOnEditorActionListener(this);
+
+        setDropdownChipLayouter(new DropdownChipLayouter(LayoutInflater.from(context), context));
+    }
+
+    protected void setDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter) {
+        mDropdownChipLayouter = dropdownChipLayouter;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mAttachedToWindow = false;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mAttachedToWindow = true;
+    }
+
+    @Override
+    public boolean onEditorAction(TextView view, int action, KeyEvent keyEvent) {
+        if (action == EditorInfo.IME_ACTION_DONE) {
+            if (commitDefault()) {
+                return true;
+            }
+            if (mSelectedChip != null) {
+                clearSelectedChip();
+                return true;
+            } else if (focusNext()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+        InputConnection connection = super.onCreateInputConnection(outAttrs);
+        int imeActions = outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION;
+        if ((imeActions&EditorInfo.IME_ACTION_DONE) != 0) {
+            // clear the existing action
+            outAttrs.imeOptions ^= imeActions;
+            // set the DONE action
+            outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
+        }
+        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*/ DrawableRecipientChip getLastChip() {
+        DrawableRecipientChip last = null;
+        DrawableRecipientChip[] chips = getSortedRecipients();
+        if (chips != null && chips.length > 0) {
+            last = chips[chips.length - 1];
+        }
+        return last;
+    }
+
+    @Override
+    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.
+        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()));
+        }
+        super.onSelectionChanged(start, end);
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (!TextUtils.isEmpty(getText())) {
+            super.onRestoreInstanceState(null);
+        } else {
+            super.onRestoreInstanceState(state);
+        }
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        // If the user changes orientation while they are editing, just roll back the selection.
+        clearSelectedChip();
+        return super.onSaveInstanceState();
+    }
+
+    /**
+     * Convenience method: Append the specified text slice to the TextView's
+     * display buffer, upgrading it to BufferType.EDITABLE if it was
+     * not already editable. Commas are excluded as they are added automatically
+     * by the view.
+     */
+    @Override
+    public void append(CharSequence text, int start, int end) {
+        // We don't care about watching text changes while appending.
+        if (mTextWatcher != null) {
+            removeTextChangedListener(mTextWatcher);
+        }
+        super.append(text, start, end);
+        if (!TextUtils.isEmpty(text) && TextUtils.getTrimmedLength(text) > 0) {
+            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(displayString);
+            }
+        }
+        // Put a message on the queue to make sure we ALWAYS handle pending
+        // chips.
+        if (mPendingChipsCount > 0) {
+            postHandlePendingChips();
+        }
+        mHandler.post(mAddTextWatcher);
+    }
+
+    @Override
+    public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
+        super.onFocusChanged(hasFocus, direction, previous);
+        if (!hasFocus) {
+            shrink();
+        } else {
+            expand();
+        }
+    }
+
+    private int getExcessTopPadding() {
+        if (sExcessTopPadding == -1) {
+            sExcessTopPadding = (int) (mChipHeight + mLineSpacingExtra);
+        }
+        return sExcessTopPadding;
+    }
+
+    @Override
+    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
+        super.setAdapter(adapter);
+        BaseRecipientAdapter baseAdapter = (BaseRecipientAdapter) adapter;
+        baseAdapter.registerUpdateObserver(new BaseRecipientAdapter.EntriesUpdatedObserver() {
+            @Override
+            public void onChanged(List<RecipientEntry> entries) {
+                // Scroll the chips field to the top of the screen so
+                // that the user can see as many results as possible.
+                if (entries != null && entries.size() > 0) {
+                    scrollBottomIntoView();
+                }
+            }
+        });
+        baseAdapter.setDropdownChipLayouter(mDropdownChipLayouter);
+    }
+
+    protected void scrollBottomIntoView() {
+        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);
+            }
+        }
+    }
+
+    protected ScrollView getScrollView() {
+        return mScrollView;
+    }
+
+    @Override
+    public void performValidation() {
+        // Do nothing. Chips handles its own validation.
+    }
+
+    private void shrink() {
+        if (mTokenizer == null) {
+            return;
+        }
+        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) {
+                // We don't have the width yet which means the view hasn't been drawn yet
+                // and there is no reason to attempt to commit chips yet.
+                // This focus lost must be the result of an orientation change
+                // or an initial rendering.
+                // Re-post the shrink for later.
+                mHandler.removeCallbacks(mDelayedShrink);
+                mHandler.post(mDelayedShrink);
+                return;
+            }
+            // Reset any pending chips as they would have been handled
+            // when the field lost focus.
+            if (mPendingChipsCount > 0) {
+                postHandlePendingChips();
+            } else {
+                Editable editable = getText();
+                int end = getSelectionEnd();
+                int start = mTokenizer.findTokenStart(editable, end);
+                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 = movePastTerminators(whatEnd);
+                    }
+                    // In the middle of chip; treat this as an edit
+                    // and commit the whole token.
+                    int selEnd = getSelectionEnd();
+                    if (whatEnd != selEnd) {
+                        handleEdit(start, whatEnd);
+                    } else {
+                        commitChip(start, end, editable);
+                    }
+                }
+            }
+            mHandler.post(mAddTextWatcher);
+        }
+        createMoreChip();
+    }
+
+    private void expand() {
+        if (mShouldShrink) {
+            setMaxLines(Integer.MAX_VALUE);
+        }
+        removeMoreChip();
+        setCursorVisible(true);
+        Editable text = getText();
+        setSelection(text != null && text.length() > 0 ? text.length() : 0);
+        // If there are any temporary chips, try replacing them now that the user
+        // has expanded the field.
+        if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0) {
+            new RecipientReplacementTask().execute();
+            mTemporaryRecipients = null;
+        }
+    }
+
+    private CharSequence ellipsizeText(CharSequence text, TextPaint paint, float maxWidth) {
+        paint.setTextSize(mChipFontSize);
+        if (maxWidth <= 0 && Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "Max width is negative: " + maxWidth);
+        }
+        return TextUtils.ellipsize(text, paint, maxWidth,
+                TextUtils.TruncateAt.END);
+    }
+
+    /**
+     * Creates a bitmap of the given contact on a selected chip.
+     *
+     * @param contact The recipient entry to pull data from.
+     * @param paint The paint to use to draw the bitmap.
+     */
+    private Bitmap createSelectedChip(RecipientEntry contact, TextPaint paint) {
+        paint.setColor(sSelectedTextColor);
+        Bitmap photo;
+        if (mDisableDelete) {
+            // Show the avatar instead if we don't want to delete
+            photo = getAvatarIcon(contact);
+        } else {
+            photo = ((BitmapDrawable) mChipDelete).getBitmap();
+        }
+        return createChipBitmap(contact, paint, photo, mChipBackgroundPressed);
+    }
+
+    /**
+     * Creates a bitmap of the given contact on a selected chip.
+     *
+     * @param contact The recipient entry to pull data from.
+     * @param paint The paint to use to draw the bitmap.
+     */
+    // TODO: Is leaveBlankIconSpacer obsolete now that we have left and right attributes?
+    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint,
+            boolean leaveBlankIconSpacer) {
+        Drawable background = getChipBackground(contact);
+        Bitmap photo = getAvatarIcon(contact);
+        paint.setColor(getContext().getResources().getColor(android.R.color.black));
+        return createChipBitmap(contact, paint, photo, background);
+    }
+
+    private Bitmap createChipBitmap(RecipientEntry contact, TextPaint paint, Bitmap icon,
+        Drawable background) {
+        if (background == null) {
+            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
+            return Bitmap.createBitmap(
+                    (int) mChipHeight * 2, (int) mChipHeight, Bitmap.Config.ARGB_8888);
+        }
+
+        Rect backgroundPadding = new Rect();
+        background.getPadding(backgroundPadding);
+
+        // 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.
+        int height = (int) mChipHeight;
+        // Since the icon is a square, it's width is equal to the maximum height it can be inside
+        // the chip.
+        int iconWidth = height - backgroundPadding.top - backgroundPadding.bottom;
+        float[] widths = new float[1];
+        paint.getTextWidths(" ", widths);
+        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
+                calculateAvailableWidth() - iconWidth - widths[0] - backgroundPadding.left
+                    - backgroundPadding.right);;
+        int textWidth = (int) paint.measureText(ellipsizedText, 0, ellipsizedText.length());
+
+        // Make sure there is a minimum chip width so the user can ALWAYS
+        // tap a chip without difficulty.
+        int width = Math.max(iconWidth * 2, textWidth + (mChipPadding * 2) + iconWidth
+                + backgroundPadding.left + backgroundPadding.right);
+
+        // Create the background of the chip.
+        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(tmpBitmap);
+
+        // Draw the background drawable
+        background.setBounds(0, 0, width, height);
+        background.draw(canvas);
+        // Draw the text vertically aligned
+        int textX = shouldPositionAvatarOnRight() ?
+                mChipPadding + backgroundPadding.left :
+                width - backgroundPadding.right - mChipPadding - textWidth;
+        canvas.drawText(ellipsizedText, 0, ellipsizedText.length(),
+                textX, getTextYOffset(ellipsizedText.toString(), paint, height), paint);
+        if (icon != null) {
+            // Draw the icon
+            int iconX = shouldPositionAvatarOnRight() ?
+                    width - backgroundPadding.right - iconWidth :
+                    backgroundPadding.left;
+            RectF src = new RectF(0, 0, icon.getWidth(), icon.getHeight());
+            RectF dst = new RectF(iconX,
+                    0 + backgroundPadding.top,
+                    iconX + iconWidth,
+                    height - backgroundPadding.bottom);
+            drawIconOnCanvas(icon, canvas, paint, src, dst);
+        }
+        return tmpBitmap;
+    }
+
+    /**
+     * Returns true if the avatar should be positioned at the right edge of the chip.
+     * Takes into account both the set avatar position (start or end) as well as whether
+     * the layout direction is LTR or RTL.
+     */
+    private boolean shouldPositionAvatarOnRight() {
+        final boolean isRtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 ?
+                getLayoutDirection() == LAYOUT_DIRECTION_RTL : false;
+        final boolean assignedPosition = mAvatarPosition == AVATAR_POSITION_END;
+        // If in Rtl mode, the position should be flipped.
+        return isRtl ? !assignedPosition : assignedPosition;
+    }
+
+    /**
+     * Returns the avatar icon to use for this recipient entry. Returns null if we don't want to
+     * draw an icon for this recipient.
+     */
+    private Bitmap getAvatarIcon(RecipientEntry 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.
+            if (photoBytes == null && contact.getPhotoThumbnailUri() != null) {
+                // TODO: cache this in the recipient entry?
+                getAdapter().fetchPhoto(contact, contact.getPhotoThumbnailUri());
+                photoBytes = contact.getPhotoBytes();
+            }
+            if (photoBytes != null) {
+                return BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
+            } else {
+                // TODO: can the scaled down default photo be cached?
+                return mDefaultContactPhoto;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the background drawable for a RecipientChip.
+     */
+    // Visible for testing.
+    /* package */Drawable getChipBackground(RecipientEntry contact) {
+        return contact.isValid() ? mChipBackground : mInvalidChipBackground;
+    }
+
+    /**
+     * Given a height, returns a Y offset that will draw the text in the middle of the height.
+     */
+    protected 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();
+    }
+
+    /**
+     * Draws the icon onto the canvas given the source rectangle of the bitmap and the destination
+     * rectangle of the canvas.
+     */
+    protected void drawIconOnCanvas(Bitmap icon, Canvas canvas, Paint paint, RectF src, RectF dst) {
+        Matrix matrix = new Matrix();
+        matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
+        canvas.drawBitmap(icon, matrix, paint);
+    }
+
+    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.");
+        }
+
+        TextPaint paint = getPaint();
+        float defaultSize = paint.getTextSize();
+        int defaultColor = paint.getColor();
+
+        Bitmap tmpBitmap;
+        if (pressed) {
+            tmpBitmap = createSelectedChip(contact, paint);
+
+        } else {
+            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());
+        DrawableRecipientChip recipientChip =
+                new VisibleRecipientChip(result, contact, getImageSpanAlignment());
+        // Return text to the original size.
+        paint.setTextSize(defaultSize);
+        paint.setColor(defaultColor);
+        return recipientChip;
+    }
+
+    private int getImageSpanAlignment() {
+        switch (mImageSpanAlignment) {
+            case IMAGE_SPAN_ALIGNMENT_BASELINE:
+                return ImageSpan.ALIGN_BASELINE;
+            case IMAGE_SPAN_ALIGNMENT_BOTTOM:
+                return ImageSpan.ALIGN_BOTTOM;
+            default:
+                return ImageSpan.ALIGN_BOTTOM;
+        }
+    }
+
+    /**
+     * Calculate the bottom of the line the chip will be located on using:
+     * 1) which line the chip appears on
+     * 2) the height of a chip
+     * 3) padding built into the edit text view
+     */
+    private int calculateOffsetFromBottom(int line) {
+        // Line offsets start at zero.
+        int actualLine = getLineCount() - (line + 1);
+        return -((actualLine * ((int) mChipHeight) + getPaddingBottom()) + getPaddingTop())
+                + getDropDownVerticalOffset();
+    }
+
+    /**
+     * Get the max amount of space a chip can take up. The formula takes into
+     * account the width of the EditTextView, any view padding, and padding
+     * that will be added to the chip.
+     */
+    private float calculateAvailableWidth() {
+        return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2);
+    }
+
+
+    private void setChipDimensions(Context context, AttributeSet attrs) {
+        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);
+        }
+        mChipBackgroundPressed = a
+                .getDrawable(R.styleable.RecipientEditTextView_chipBackgroundPressed);
+        if (mChipBackgroundPressed == null) {
+            mChipBackgroundPressed = r.getDrawable(R.drawable.chip_background_selected);
+        }
+        mChipDelete = a.getDrawable(R.styleable.RecipientEditTextView_chipDelete);
+        if (mChipDelete == null) {
+            mChipDelete = r.getDrawable(R.drawable.chip_delete);
+        }
+        mChipPadding = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipPadding, -1);
+        if (mChipPadding == -1) {
+            mChipPadding = (int) r.getDimension(R.dimen.chip_padding);
+        }
+
+        mDefaultContactPhoto = BitmapFactory.decodeResource(r, R.drawable.ic_contact_picture);
+
+        mMoreItem = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.more_item, null);
+
+        mChipHeight = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipHeight, -1);
+        if (mChipHeight == -1) {
+            mChipHeight = r.getDimension(R.dimen.chip_height);
+        }
+        mChipFontSize = a.getDimensionPixelSize(R.styleable.RecipientEditTextView_chipFontSize, -1);
+        if (mChipFontSize == -1) {
+            mChipFontSize = r.getDimension(R.dimen.chip_text_size);
+        }
+        mInvalidChipBackground = a
+                .getDrawable(R.styleable.RecipientEditTextView_invalidChipBackground);
+        if (mInvalidChipBackground == null) {
+            mInvalidChipBackground = r.getDrawable(R.drawable.chip_background_invalid);
+        }
+        mAvatarPosition = a.getInt(R.styleable.RecipientEditTextView_avatarPosition, 0);
+        mImageSpanAlignment = a.getInt(R.styleable.RecipientEditTextView_imageSpanAlignment, 0);
+        mDisableDelete = a.getBoolean(R.styleable.RecipientEditTextView_disableDelete, false);
+
+        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();
+    }
+
+    // Visible for testing.
+    /* package */ void setMoreItem(TextView moreItem) {
+        mMoreItem = moreItem;
+    }
+
+
+    // Visible for testing.
+    /* package */ void setChipBackground(Drawable chipBackground) {
+        mChipBackground = chipBackground;
+    }
+
+    // Visible for testing.
+    /* package */ void setChipHeight(int height) {
+        mChipHeight = height;
+    }
+
+    public float getChipHeight() {
+        return mChipHeight;
+    }
+
+    /**
+     * Set whether to shrink the recipients field such that at most
+     * one line of recipients chips are shown when the field loses
+     * focus. By default, the number of displayed recipients will be
+     * limited and a "more" chip will be shown when focus is lost.
+     * @param shrink
+     */
+    public void setOnFocusListShrinkRecipients(boolean shrink) {
+        mShouldShrink = shrink;
+    }
+
+    @Override
+    public void onSizeChanged(int width, int height, int oldw, int oldh) {
+        super.onSizeChanged(width, height, oldw, oldh);
+        if (width != 0 && height != 0) {
+            if (mPendingChipsCount > 0) {
+                postHandlePendingChips();
+            } else {
+                checkChipWidths();
+            }
+        }
+        // Try to find the scroll view parent, if it exists.
+        if (mScrollView == null && !mTriedGettingScrollView) {
+            ViewParent parent = getParent();
+            while (parent != null && !(parent instanceof ScrollView)) {
+                parent = parent.getParent();
+            }
+            if (parent != null) {
+                mScrollView = (ScrollView) parent;
+            }
+            mTriedGettingScrollView = true;
+        }
+    }
+
+    private void postHandlePendingChips() {
+        mHandler.removeCallbacks(mHandlePendingChips);
+        mHandler.post(mHandlePendingChips);
+    }
+
+    private void checkChipWidths() {
+        // Check the widths of the associated chips.
+        DrawableRecipientChip[] chips = getSortedRecipients();
+        if (chips != null) {
+            Rect bounds;
+            for (DrawableRecipientChip chip : chips) {
+                bounds = chip.getBounds();
+                if (getWidth() > 0 && bounds.right - bounds.left >
+                        getWidth() - getPaddingLeft() - getPaddingRight()) {
+                    // Need to redraw that chip.
+                    replaceChip(chip, chip.getEntry());
+                }
+            }
+        }
+    }
+
+    // Visible for testing.
+    /*package*/ void handlePendingChips() {
+        if (getViewWidth() <= 0) {
+            // The widget has not been sized yet.
+            // This will be called as a result of onSizeChanged
+            // at a later point.
+            return;
+        }
+        if (mPendingChipsCount <= 0) {
+            return;
+        }
+
+        synchronized (mPendingChips) {
+            Editable editable = getText();
+            // Tokenize!
+            if (mPendingChipsCount <= MAX_CHIPS_PARSED) {
+                for (int i = 0; i < mPendingChips.size(); i++) {
+                    String current = mPendingChips.get(i);
+                    int tokenStart = editable.toString().indexOf(current);
+                    // 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.
+                        if (tokenEnd < editable.length() - 2
+                                && editable.charAt(tokenEnd) == COMMIT_CHAR_COMMA) {
+                            tokenEnd++;
+                        }
+                        createReplacementChip(tokenStart, tokenEnd, editable, i < CHIP_LIMIT
+                                || !mShouldShrink);
+                    }
+                    mPendingChipsCount--;
+                }
+                sanitizeEnd();
+            } else {
+                mNoChips = true;
+            }
+
+            if (mTemporaryRecipients != null && mTemporaryRecipients.size() > 0
+                    && mTemporaryRecipients.size() <= RecipientAlternatesAdapter.MAX_LOOKUPS) {
+                if (hasFocus() || mTemporaryRecipients.size() < CHIP_LIMIT) {
+                    new RecipientReplacementTask().execute();
+                    mTemporaryRecipients = null;
+                } else {
+                    // Create the "more" chip
+                    mIndividualReplacements = new IndividualReplacementTask();
+                    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 {
+                // There are too many recipients to look up, so just fall back
+                // to showing addresses for all of them.
+                mTemporaryRecipients = null;
+                createMoreChip();
+            }
+            mPendingChipsCount = 0;
+            mPendingChips.clear();
+        }
+    }
+
+    // Visible for testing.
+    /*package*/ int getViewWidth() {
+        return getWidth();
+    }
+
+    /**
+     * Remove any characters after the last valid chip.
+     */
+    // Visible for testing.
+    /*package*/ void sanitizeEnd() {
+        // Don't sanitize while we are waiting for pending chips to complete.
+        if (mPendingChipsCount > 0) {
+            return;
+        }
+        // Find the last chip; eliminate any commit characters after it.
+        DrawableRecipientChip[] chips = getSortedRecipients();
+        Spannable spannable = getSpannable();
+        if (chips != null && chips.length > 0) {
+            int end;
+            mMoreChip = getMoreChip();
+            if (mMoreChip != null) {
+                end = spannable.getSpanEnd(mMoreChip);
+            } else {
+                end = getSpannable().getSpanEnd(getLastChip());
+            }
+            Editable editable = getText();
+            int length = editable.length();
+            if (length > end) {
+                // See what characters occur after that and eliminate them.
+                if (Log.isLoggable(TAG, Log.DEBUG)) {
+                    Log.d(TAG, "There were extra characters after the last tokenizable entry."
+                            + editable);
+                }
+                editable.delete(end + 1, length);
+            }
+        }
+    }
+
+    /**
+     * 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.
+     */
+    // 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);
+        final String trimmedToken = token.trim();
+        int commitCharIndex = trimmedToken.lastIndexOf(COMMIT_CHAR_COMMA);
+        if (commitCharIndex != -1 && commitCharIndex == trimmedToken.length() - 1) {
+            token = trimmedToken.substring(0, trimmedToken.length() - 1);
+        }
+        RecipientEntry entry = createTokenizedEntry(token);
+        if (entry != null) {
+            DrawableRecipientChip chip = null;
+            try {
+                if (!mNoChips) {
+                    /*
+                     * 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.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<DrawableRecipientChip>();
+                }
+                chip.setOriginalText(token);
+                mTemporaryRecipients.add(chip);
+            }
+        }
+    }
+
+    private static boolean isPhoneNumber(String number) {
+        // TODO: replace this function with libphonenumber's isPossibleNumber (see
+        // PhoneNumberUtil). One complication is that it requires the sender's region which
+        // comes from the CurrentCountryIso. For now, let's just do this simple match.
+        if (TextUtils.isEmpty(number)) {
+            return false;
+        }
+
+        Matcher match = PHONE_PATTERN.matcher(number);
+        return match.matches();
+    }
+
+    // VisibleForTesting
+    RecipientEntry createTokenizedEntry(final String token) {
+        if (TextUtils.isEmpty(token)) {
+            return null;
+        }
+        if (isPhoneQuery() && isPhoneNumber(token)) {
+            return RecipientEntry.constructFakePhoneEntry(token, true);
+        }
+        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(token);
+        String display = null;
+        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, tokens[0].getAddress(),
+                        isValid);
+            } else {
+                display = tokens[0].getAddress();
+                if (!TextUtils.isEmpty(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 && !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,
+                    // 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
+                    // 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, isValid);
+    }
+
+    private boolean isValid(String text) {
+        return mValidator == null ? true : mValidator.isValid(text);
+    }
+
+    private static String tokenizeAddress(String destination) {
+        Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(destination);
+        if (tokens != null && tokens.length > 0) {
+            return tokens[0].getAddress();
+        }
+        return destination;
+    }
+
+    @Override
+    public void setTokenizer(Tokenizer tokenizer) {
+        mTokenizer = tokenizer;
+        super.setTokenizer(mTokenizer);
+    }
+
+    @Override
+    public void setValidator(Validator validator) {
+        mValidator = validator;
+        super.setValidator(validator);
+    }
+
+    /**
+     * We cannot use the default mechanism for replaceText. Instead,
+     * we override onItemClickListener so we can get all the associated
+     * contact information including display text, address, and id.
+     */
+    @Override
+    protected void replaceText(CharSequence text) {
+        return;
+    }
+
+    /**
+     * Dismiss any selected chips when the back key is pressed.
+     */
+    @Override
+    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK && mSelectedChip != null) {
+            clearSelectedChip();
+            return true;
+        }
+        return super.onKeyPreIme(keyCode, event);
+    }
+
+    /**
+     * Monitor key presses in this view to see if the user types
+     * any commit keys, which consist of ENTER, TAB, or DPAD_CENTER.
+     * If the user has entered text that has contact matches and types
+     * a commit key, create a chip from the topmost matching contact.
+     * If the user has entered text that has no contact matches and types
+     * a commit key, then create a chip from the text they have entered.
+     */
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_TAB:
+                if (event.hasNoModifiers()) {
+                    if (mSelectedChip != null) {
+                        clearSelectedChip();
+                    } else {
+                        commitDefault();
+                    }
+                }
+                break;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    private boolean focusNext() {
+        View next = focusSearch(View.FOCUS_DOWN);
+        if (next != null) {
+            next.requestFocus();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Create a chip from the default selection. If the popup is showing, the
+     * default is the first item in the popup suggestions list. Otherwise, it is
+     * whatever the user had typed in. End represents where the the tokenizer
+     * should search for a token to turn into a chip.
+     * @return If a chip was created from a real contact.
+     */
+    private boolean commitDefault() {
+        // If there is no tokenizer, don't try to commit.
+        if (mTokenizer == null) {
+            return false;
+        }
+        Editable editable = getText();
+        int end = getSelectionEnd();
+        int start = mTokenizer.findTokenStart(editable, end);
+
+        if (shouldCreateChip(start, end)) {
+            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;
+            }
+            return commitChip(start, end , editable);
+        }
+        return false;
+    }
+
+    private void commitByCharacter() {
+        // We can't possibly commit by character if we can't tokenize.
+        if (mTokenizer == null) {
+            return;
+        }
+        Editable editable = getText();
+        int end = getSelectionEnd();
+        int start = mTokenizer.findTokenStart(editable, end);
+        if (shouldCreateChip(start, end)) {
+            commitChip(start, end, editable);
+        }
+        setSelection(getText().length());
+    }
+
+    private boolean commitChip(int start, int end, Editable editable) {
+        ListAdapter adapter = getAdapter();
+        if (adapter != null && adapter.getCount() > 0 && enoughToFilter()
+                && end == getSelectionEnd() && !isPhoneQuery()) {
+            // choose the first entry.
+            submitItemAtPosition(0);
+            dismissDropDown();
+            return true;
+        } else {
+            int tokenEnd = mTokenizer.findTokenEnd(editable, start);
+            if (editable.length() > tokenEnd + 1) {
+                char charAt = editable.charAt(tokenEnd + 1);
+                if (charAt == COMMIT_CHAR_COMMA || charAt == COMMIT_CHAR_SEMICOLON) {
+                    tokenEnd++;
+                }
+            }
+            String text = editable.toString().substring(start, tokenEnd).trim();
+            clearComposingText();
+            if (text != null && text.length() > 0 && !text.equals(" ")) {
+                RecipientEntry entry = createTokenizedEntry(text);
+                if (entry != null) {
+                    QwertyKeyListener.markAsReplaced(editable, start, end, "");
+                    CharSequence chipText = createChip(entry, false);
+                    if (chipText != null && start > -1 && end > -1) {
+                        editable.replace(start, end, chipText);
+                    }
+                }
+                // Only dismiss the dropdown if it is related to the text we
+                // just committed.
+                // For paste, it may not be as there are possibly multiple
+                // tokens being added.
+                if (end == getSelectionEnd()) {
+                    dismissDropDown();
+                }
+                sanitizeBetween();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Visible for testing.
+    /* package */ void sanitizeBetween() {
+        // Don't sanitize while we are waiting for content to chipify.
+        if (mPendingChipsCount > 0) {
+            return;
+        }
+        // Find the last chip.
+        DrawableRecipientChip[] recips = getSortedRecipients();
+        if (recips != null && recips.length > 0) {
+            DrawableRecipientChip last = recips[recips.length - 1];
+            DrawableRecipientChip beforeLast = null;
+            if (recips.length > 1) {
+                beforeLast = recips[recips.length - 2];
+            }
+            int startLooking = 0;
+            int end = getSpannable().getSpanStart(last);
+            if (beforeLast != null) {
+                startLooking = getSpannable().getSpanEnd(beforeLast);
+                Editable text = getText();
+                if (startLooking == -1 || startLooking > text.length() - 1) {
+                    // There is nothing after this chip.
+                    return;
+                }
+                if (text.charAt(startLooking) == ' ') {
+                    startLooking++;
+                }
+            }
+            if (startLooking >= 0 && end >= 0 && startLooking < end) {
+                getText().delete(startLooking, end);
+            }
+        }
+    }
+
+    private boolean shouldCreateChip(int start, int end) {
+        return !mNoChips && hasFocus() && enoughToFilter() && !alreadyHasChip(start, end);
+    }
+
+    private boolean alreadyHasChip(int start, int end) {
+        if (mNoChips) {
+            return true;
+        }
+        DrawableRecipientChip[] chips =
+                getSpannable().getSpans(start, end, DrawableRecipientChip.class);
+        if ((chips == null || chips.length == 0)) {
+            return false;
+        }
+        return true;
+    }
+
+    private void handleEdit(int start, int end) {
+        if (start == -1 || end == -1) {
+            // This chip no longer exists in the field.
+            dismissDropDown();
+            return;
+        }
+        // This is in the middle of a chip, so select out the whole chip
+        // and commit it.
+        Editable editable = getText();
+        setSelection(end);
+        String text = getText().toString().substring(start, end);
+        if (!TextUtils.isEmpty(text)) {
+            RecipientEntry entry = RecipientEntry.constructFakeEntry(text, isValid(text));
+            QwertyKeyListener.markAsReplaced(editable, start, end, "");
+            CharSequence chipText = createChip(entry, false);
+            int selEnd = getSelectionEnd();
+            if (chipText != null && start > -1 && selEnd > -1) {
+                editable.replace(start, selEnd, chipText);
+            }
+        }
+        dismissDropDown();
+    }
+
+    /**
+     * If there is a selected chip, delegate the key events
+     * to the selected chip.
+     */
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (mSelectedChip != null && keyCode == KeyEvent.KEYCODE_DEL) {
+            if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
+                mAlternatesPopup.dismiss();
+            }
+            removeChip(mSelectedChip);
+        }
+
+        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);
+    }
+
+    // Visible for testing.
+    /* package */ Spannable getSpannable() {
+        return getText();
+    }
+
+    private int getChipStart(DrawableRecipientChip chip) {
+        return getSpannable().getSpanStart(chip);
+    }
+
+    private int getChipEnd(DrawableRecipientChip chip) {
+        return getSpannable().getSpanEnd(chip);
+    }
+
+    /**
+     * Instead of filtering on the entire contents of the edit box,
+     * this subclass method filters on the range from
+     * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
+     * if the length of that range meets or exceeds {@link #getThreshold}
+     * and makes sure that the range is not already a Chip.
+     */
+    @Override
+    protected void performFiltering(CharSequence text, int keyCode) {
+        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();
+            DrawableRecipientChip[] chips = span.getSpans(start, end, DrawableRecipientChip.class);
+            if (chips != null && chips.length > 0) {
+                dismissDropDown();
+                return;
+            }
+        } else if (isCompletedToken) {
+            dismissDropDown();
+            return;
+        }
+        super.performFiltering(text, keyCode);
+    }
+
+    // Visible for testing.
+    /*package*/ boolean isCompletedToken(CharSequence text) {
+        if (TextUtils.isEmpty(text)) {
+            return false;
+        }
+        // Check to see if this is a completed token before filtering.
+        int end = text.length();
+        int start = mTokenizer.findTokenStart(text, end);
+        String token = text.toString().substring(start, end).trim();
+        if (!TextUtils.isEmpty(token)) {
+            char atEnd = token.charAt(token.length() - 1);
+            return atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON;
+        }
+        return false;
+    }
+
+    private void clearSelectedChip() {
+        if (mSelectedChip != null) {
+            unselectChip(mSelectedChip);
+            mSelectedChip = null;
+        }
+        setCursorVisible(true);
+    }
+
+    /**
+     * Monitor touch events in the RecipientEditTextView.
+     * If the view does not have focus, any tap on the view
+     * will just focus the view. If the view has focus, determine
+     * if the touch target is a recipient chip. If it is and the chip
+     * is not selected, select it and clear any other selected chips.
+     * If it isn't, then select that chip.
+     */
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (!isFocused()) {
+            // Ignore any chip taps until this view is focused.
+            return super.onTouchEvent(event);
+        }
+        boolean handled = super.onTouchEvent(event);
+        int action = event.getAction();
+        boolean chipWasSelected = false;
+        if (mSelectedChip == null) {
+            mGestureDetector.onTouchEvent(event);
+        }
+        if (mCopyAddress == null && action == MotionEvent.ACTION_UP) {
+            float x = event.getX();
+            float y = event.getY();
+            int offset = putOffsetInRange(x, y);
+            DrawableRecipientChip currentChip = findChip(offset);
+            if (currentChip != null) {
+                if (action == MotionEvent.ACTION_UP) {
+                    if (mSelectedChip != null && mSelectedChip != currentChip) {
+                        clearSelectedChip();
+                        mSelectedChip = selectChip(currentChip);
+                    } else if (mSelectedChip == null) {
+                        setSelection(getText().length());
+                        commitDefault();
+                        mSelectedChip = selectChip(currentChip);
+                    } else {
+                        onClick(mSelectedChip, offset, x, y);
+                    }
+                }
+                chipWasSelected = true;
+                handled = true;
+            } else if (mSelectedChip != null && shouldShowEditableText(mSelectedChip)) {
+                chipWasSelected = true;
+            }
+        }
+        if (action == MotionEvent.ACTION_UP && !chipWasSelected) {
+            clearSelectedChip();
+        }
+        return handled;
+    }
+
+    private void scrollLineIntoView(int line) {
+        if (mScrollView != null) {
+            mScrollView.smoothScrollBy(0, calculateOffsetFromBottom(line));
+        }
+    }
+
+    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);
+            }
+
+            @Override
+            protected void onPostExecute(final ListAdapter result) {
+                if (!mAttachedToWindow) {
+                    return;
+                }
+                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(DrawableRecipientChip chip) {
+        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(),
+                chip.getDirectoryId(), chip.getLookupKey(), chip.getDataId(),
+                getAdapter().getQueryType(), this, mDropdownChipLayouter);
+    }
+
+    private ListAdapter createSingleAddressAdapter(DrawableRecipientChip currentChip) {
+        return new SingleRecipientArrayAdapter(getContext(), currentChip.getEntry(),
+                mDropdownChipLayouter);
+    }
+
+    @Override
+    public void onCheckedItemChanged(int position) {
+        ListView listView = mAlternatesPopup.getListView();
+        if (listView != null && listView.getCheckedItemCount() == 0) {
+            listView.setItemChecked(position, true);
+        }
+        mCheckedItem = position;
+    }
+
+    private int putOffsetInRange(final float x, final float y) {
+        final int offset;
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            offset = getOffsetForPosition(x, y);
+        } else {
+            offset = supportGetOffsetForPosition(x, y);
+        }
+
+        return putOffsetInRange(offset);
+    }
+
+    // TODO: This algorithm will need a lot of tweaking after more people have used
+    // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring
+    // what comes before the finger.
+    private int putOffsetInRange(int o) {
+        int offset = o;
+        Editable text = getText();
+        int length = text.length();
+        // Remove whitespace from end to find "real end"
+        int realLength = length;
+        for (int i = length - 1; i >= 0; i--) {
+            if (text.charAt(i) == ' ') {
+                realLength--;
+            } else {
+                break;
+            }
+        }
+
+        // If the offset is beyond or at the end of the text,
+        // leave it alone.
+        if (offset >= realLength) {
+            return offset;
+        }
+        Editable editable = getText();
+        while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) {
+            // Keep walking backward!
+            offset--;
+        }
+        return offset;
+    }
+
+    private static int findText(Editable text, int offset) {
+        if (text.charAt(offset) != ' ') {
+            return offset;
+        }
+        return -1;
+    }
+
+    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++) {
+            DrawableRecipientChip chip = chips[i];
+            int start = getChipStart(chip);
+            int end = getChipEnd(chip);
+            if (offset >= start && offset <= end) {
+                return chip;
+            }
+        }
+        return null;
+    }
+
+    // Visible for testing.
+    // Use this method to generate text to add to the list of addresses.
+    /* package */String createAddressText(RecipientEntry entry) {
+        String display = entry.getDisplayName();
+        String address = entry.getDestination();
+        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
+            display = null;
+        }
+        String trimmedDisplayText;
+        if (isPhoneQuery() && isPhoneNumber(address)) {
+            trimmedDisplayText = address.trim();
+        } else {
+            if (address != null) {
+                // 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();
+                }
+            }
+            Rfc822Token token = new Rfc822Token(display, address, null);
+            trimmedDisplayText = token.toString().trim();
+        }
+        int index = trimmedDisplayText.indexOf(",");
+        return mTokenizer != null && !TextUtils.isEmpty(trimmedDisplayText)
+                && index < trimmedDisplayText.length() - 1 ? (String) mTokenizer
+                .terminateToken(trimmedDisplayText) : trimmedDisplayText;
+    }
+
+    // Visible for testing.
+    // Use this method to generate text to display in a chip.
+    /*package*/ String createChipDisplayText(RecipientEntry entry) {
+        String display = entry.getDisplayName();
+        String address = entry.getDestination();
+        if (TextUtils.isEmpty(display) || TextUtils.equals(display, address)) {
+            display = null;
+        }
+        if (!TextUtils.isEmpty(display)) {
+            return display;
+        } else if (!TextUtils.isEmpty(address)){
+            return address;
+        } else {
+            return new Rfc822Token(display, address, null).toString();
+        }
+    }
+
+    private CharSequence createChip(RecipientEntry entry, boolean pressed) {
+        String displayText = createAddressText(entry);
+        if (TextUtils.isEmpty(displayText)) {
+            return null;
+        }
+        SpannableString chipText = null;
+        // Always leave a blank space at the end of a chip.
+        int textLength = displayText.length() - 1;
+        chipText = new SpannableString(displayText);
+        if (!mNoChips) {
+            try {
+                DrawableRecipientChip chip = constructChipSpan(entry, pressed,
+                        false /* leave space for contact icon */);
+                chipText.setSpan(chip, 0, textLength,
+                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                chip.setOriginalText(chipText.toString());
+            } catch (NullPointerException e) {
+                Log.e(TAG, e.getMessage(), e);
+                return null;
+            }
+        }
+        return chipText;
+    }
+
+    /**
+     * When an item in the suggestions list has been clicked, create a chip from the
+     * contact information of the selected item.
+     */
+    @Override
+    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        if (position < 0) {
+            return;
+        }
+        submitItemAtPosition(position);
+    }
+
+    private void submitItemAtPosition(int position) {
+        RecipientEntry entry = createValidatedEntry(getAdapter().getItem(position));
+        if (entry == null) {
+            return;
+        }
+        clearComposingText();
+
+        int end = getSelectionEnd();
+        int start = mTokenizer.findTokenStart(getText(), end);
+
+        Editable editable = getText();
+        QwertyKeyListener.markAsReplaced(editable, start, end, "");
+        CharSequence chip = createChip(entry, false);
+        if (chip != null && start >= 0 && end >= 0) {
+            editable.replace(start, end, chip);
+        }
+        sanitizeBetween();
+    }
+
+    private RecipientEntry createValidatedEntry(RecipientEntry item) {
+        if (item == null) {
+            return null;
+        }
+        final RecipientEntry entry;
+        // If the display name and the address are the same, or if this is a
+        // valid contact, but the destination is invalid, then make this a fake
+        // recipient that is editable.
+        String destination = item.getDestination();
+        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, item.isValid());
+        } else {
+            entry = item;
+        }
+        return entry;
+    }
+
+    /** Returns a collection of contact Id for each chip inside this View. */
+    /* package */ Collection<Long> getContactIds() {
+        final Set<Long> result = new HashSet<Long>();
+        DrawableRecipientChip[] chips = getSortedRecipients();
+        if (chips != null) {
+            for (DrawableRecipientChip chip : chips) {
+                result.add(chip.getContactId());
+            }
+        }
+        return result;
+    }
+
+
+    /** 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>();
+        DrawableRecipientChip [] chips = getSortedRecipients();
+        if (chips != null) {
+            for (DrawableRecipientChip chip : chips) {
+                result.add(chip.getDataId());
+            }
+        }
+        return result;
+    }
+
+    // Visible for testing.
+    /* 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<DrawableRecipientChip>() {
+
+            @Override
+            public int compare(DrawableRecipientChip first, DrawableRecipientChip second) {
+                int firstStart = spannable.getSpanStart(first);
+                int secondStart = spannable.getSpanStart(second);
+                if (firstStart < secondStart) {
+                    return -1;
+                } else if (firstStart > secondStart) {
+                    return 1;
+                } else {
+                    return 0;
+                }
+            }
+        });
+        return recipientsList.toArray(new DrawableRecipientChip[recipientsList.size()]);
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        return false;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+    }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        return false;
+    }
+
+    /**
+     * No chips are selectable.
+     */
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        return false;
+    }
+
+    // Visible for testing.
+    /* package */ImageSpan getMoreChip() {
+        MoreImageSpan[] moreSpans = getSpannable().getSpans(0, getText().length(),
+                MoreImageSpan.class);
+        return moreSpans != null && moreSpans.length > 0 ? moreSpans[0] : null;
+    }
+
+    private MoreImageSpan createMoreSpan(int count) {
+        String moreText = String.format(mMoreItem.getText().toString(), count);
+        TextPaint morePaint = new TextPaint(getPaint());
+        morePaint.setTextSize(mMoreItem.getTextSize());
+        morePaint.setColor(mMoreItem.getCurrentTextColor());
+        int width = (int)morePaint.measureText(moreText) + mMoreItem.getPaddingLeft()
+                + mMoreItem.getPaddingRight();
+        int height = getLineHeight();
+        Bitmap drawable = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(drawable);
+        int adjustedHeight = height;
+        Layout layout = getLayout();
+        if (layout != null) {
+            adjustedHeight -= layout.getLineDescent(0);
+        }
+        canvas.drawText(moreText, 0, moreText.length(), 0, adjustedHeight, morePaint);
+
+        Drawable result = new BitmapDrawable(getResources(), drawable);
+        result.setBounds(0, 0, width, height);
+        return new MoreImageSpan(result);
+    }
+
+    // Visible for testing.
+    /*package*/ void createMoreChipPlainText() {
+        // Take the first <= CHIP_LIMIT addresses and get to the end of the second one.
+        Editable text = getText();
+        int start = 0;
+        int end = start;
+        for (int i = 0; i < CHIP_LIMIT; i++) {
+            end = movePastTerminators(mTokenizer.findTokenEnd(text, start));
+            start = end; // move to the next token and get its end.
+        }
+        // Now, count total addresses.
+        start = 0;
+        int tokenCount = countTokens(text);
+        MoreImageSpan moreSpan = createMoreSpan(tokenCount - CHIP_LIMIT);
+        SpannableString chipText = new SpannableString(text.subSequence(end, text.length()));
+        chipText.setSpan(moreSpan, 0, chipText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        text.replace(end, text.length(), chipText);
+        mMoreChip = moreSpan;
+    }
+
+    // Visible for testing.
+    /* package */int countTokens(Editable text) {
+        int tokenCount = 0;
+        int start = 0;
+        while (start < text.length()) {
+            start = movePastTerminators(mTokenizer.findTokenEnd(text, start));
+            tokenCount++;
+            if (start >= text.length()) {
+                break;
+            }
+        }
+        return tokenCount;
+    }
+
+    /**
+     * Create the more chip. The more chip is text that replaces any chips that
+     * do not fit in the pre-defined available space when the
+     * RecipientEditTextView loses focus.
+     */
+    // Visible for testing.
+    /* package */ void createMoreChip() {
+        if (mNoChips) {
+            createMoreChipPlainText();
+            return;
+        }
+
+        if (!mShouldShrink) {
+            return;
+        }
+        ImageSpan[] tempMore = getSpannable().getSpans(0, getText().length(), MoreImageSpan.class);
+        if (tempMore.length > 0) {
+            getSpannable().removeSpan(tempMore[0]);
+        }
+        DrawableRecipientChip[] recipients = getSortedRecipients();
+
+        if (recipients == null || recipients.length <= CHIP_LIMIT) {
+            mMoreChip = null;
+            return;
+        }
+        Spannable spannable = getSpannable();
+        int numRecipients = recipients.length;
+        int overage = numRecipients - CHIP_LIMIT;
+        MoreImageSpan moreSpan = createMoreSpan(overage);
+        mRemovedSpans = new ArrayList<DrawableRecipientChip>();
+        int totalReplaceStart = 0;
+        int totalReplaceEnd = 0;
+        Editable text = getText();
+        for (int i = numRecipients - overage; i < recipients.length; i++) {
+            mRemovedSpans.add(recipients[i]);
+            if (i == numRecipients - overage) {
+                totalReplaceStart = spannable.getSpanStart(recipients[i]);
+            }
+            if (i == recipients.length - 1) {
+                totalReplaceEnd = spannable.getSpanEnd(recipients[i]);
+            }
+            if (mTemporaryRecipients == null || !mTemporaryRecipients.contains(recipients[i])) {
+                int spanStart = spannable.getSpanStart(recipients[i]);
+                int spanEnd = spannable.getSpanEnd(recipients[i]);
+                recipients[i].setOriginalText(text.toString().substring(spanStart, spanEnd));
+            }
+            spannable.removeSpan(recipients[i]);
+        }
+        if (totalReplaceEnd < text.length()) {
+            totalReplaceEnd = text.length();
+        }
+        int end = Math.max(totalReplaceStart, totalReplaceEnd);
+        int start = Math.min(totalReplaceStart, totalReplaceEnd);
+        SpannableString chipText = new SpannableString(text.subSequence(start, end));
+        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());
+        }
+    }
+
+    /**
+     * Replace the more chip, if it exists, with all of the recipient chips it had
+     * replaced when the RecipientEditTextView gains focus.
+     */
+    // Visible for testing.
+    /*package*/ void removeMoreChip() {
+        if (mMoreChip != null) {
+            Spannable span = getSpannable();
+            span.removeSpan(mMoreChip);
+            mMoreChip = null;
+            // Re-add the spans that were removed.
+            if (mRemovedSpans != null && mRemovedSpans.size() > 0) {
+                // Recreate each removed span.
+                DrawableRecipientChip[] recipients = getSortedRecipients();
+                // Start the search for tokens after the last currently visible
+                // chip.
+                if (recipients == null || recipients.length == 0) {
+                    return;
+                }
+                int end = span.getSpanEnd(recipients[recipients.length - 1]);
+                Editable editable = getText();
+                for (DrawableRecipientChip chip : mRemovedSpans) {
+                    int chipStart;
+                    int chipEnd;
+                    String token;
+                    // Need to find the location of the chip, again.
+                    token = (String) chip.getOriginalText();
+                    // As we find the matching recipient for the remove spans,
+                    // reduce the size of the string we need to search.
+                    // That way, if there are duplicates, we always find the correct
+                    // recipient.
+                    chipStart = editable.toString().indexOf(token, end);
+                    end = chipEnd = Math.min(editable.length(), chipStart + token.length());
+                    // Only set the span if we found a matching token.
+                    if (chipStart != -1) {
+                        editable.setSpan(chip, chipStart, chipEnd,
+                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
+                }
+                mRemovedSpans.clear();
+            }
+        }
+    }
+
+    /**
+     * Show specified chip as selected. If the RecipientChip is just an email address,
+     * selecting the chip will take the contents of the chip and place it at
+     * the end of the RecipientEditTextView for inline editing. If the
+     * RecipientChip is a complete contact, then selecting the chip
+     * will change the background color of the chip, show the delete icon,
+     * and a popup window with the address in use highlighted and any other
+     * alternate addresses for the contact.
+     * @param currentChip Chip to select.
+     * @return A RecipientChip in the selected state or null if the chip
+     * just contained an email address.
+     */
+    private DrawableRecipientChip selectChip(DrawableRecipientChip currentChip) {
+        if (shouldShowEditableText(currentChip)) {
+            CharSequence text = currentChip.getValue();
+            Editable editable = getText();
+            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());
+            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);
+            DrawableRecipientChip newChip;
+            try {
+                if (mNoChips) {
+                    return null;
+                }
+                newChip = constructChipSpan(currentChip.getEntry(), true, false);
+            } catch (NullPointerException e) {
+                Log.e(TAG, e.getMessage(), e);
+                return null;
+            }
+            Editable editable = getText();
+            QwertyKeyListener.markAsReplaced(editable, start, end, "");
+            if (start == -1 || end == -1) {
+                Log.d(TAG, "The chip being selected no longer exists but should.");
+            } else {
+                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            newChip.setSelected(true);
+            if (shouldShowEditableText(newChip)) {
+                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
+            }
+            showAddress(newChip, mAddressPopup, getWidth());
+            setCursorVisible(false);
+            return newChip;
+        } else {
+            int start = getChipStart(currentChip);
+            int end = getChipEnd(currentChip);
+            getSpannable().removeSpan(currentChip);
+            DrawableRecipientChip newChip;
+            try {
+                newChip = constructChipSpan(currentChip.getEntry(), true, false);
+            } catch (NullPointerException e) {
+                Log.e(TAG, e.getMessage(), e);
+                return null;
+            }
+            Editable editable = getText();
+            QwertyKeyListener.markAsReplaced(editable, start, end, "");
+            if (start == -1 || end == -1) {
+                Log.d(TAG, "The chip being selected no longer exists but should.");
+            } else {
+                editable.setSpan(newChip, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+            newChip.setSelected(true);
+            if (shouldShowEditableText(newChip)) {
+                scrollLineIntoView(getLayout().getLineForOffset(getChipStart(newChip)));
+            }
+            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 DrawableRecipientChip currentChip, final ListPopupWindow popup,
+            int width) {
+        if (!mAttachedToWindow) {
+            return;
+        }
+        int line = getLayout().getLineForOffset(getChipStart(currentChip));
+        int bottom = calculateOffsetFromBottom(line);
+        // Align the alternates popup with the left side of the View,
+        // regardless of the position of the chip tapped.
+        popup.setWidth(width);
+        popup.setAnchorView(this);
+        popup.setVerticalOffset(bottom);
+        popup.setAdapter(createSingleAddressAdapter(currentChip));
+        popup.setOnItemClickListener(new OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                unselectChip(currentChip);
+                popup.dismiss();
+            }
+        });
+        popup.show();
+        ListView listView = popup.getListView();
+        listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+        listView.setItemChecked(0, true);
+    }
+
+    /**
+     * Remove selection from this chip. Unselecting a RecipientChip will render
+     * the chip without a delete icon and with an unfocused background. This is
+     * called when the RecipientChip no longer has focus.
+     */
+    private void unselectChip(DrawableRecipientChip chip) {
+        int start = getChipStart(chip);
+        int end = getChipEnd(chip);
+        Editable editable = getText();
+        mSelectedChip = null;
+        if (start == -1 || end == -1) {
+            Log.w(TAG, "The chip doesn't exist or may be a chip a user was editing");
+            setSelection(editable.length());
+            commitDefault();
+        } else {
+            getSpannable().removeSpan(chip);
+            QwertyKeyListener.markAsReplaced(editable, start, end, "");
+            editable.removeSpan(chip);
+            try {
+                if (!mNoChips) {
+                    editable.setSpan(constructChipSpan(chip.getEntry(), false, false),
+                            start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+            } catch (NullPointerException e) {
+                Log.e(TAG, e.getMessage(), e);
+            }
+        }
+        setCursorVisible(true);
+        setSelection(editable.length());
+        if (mAlternatesPopup != null && mAlternatesPopup.isShowing()) {
+            mAlternatesPopup.dismiss();
+        }
+    }
+
+    /**
+     * Return whether a touch event was inside the delete target of
+     * a selected chip. It is in the delete target if:
+     * 1) the x and y points of the event are within the
+     * delete assset.
+     * 2) the point tapped would have caused a cursor to appear
+     * right after the selected chip.
+     * @return boolean
+     */
+    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?
+        if (mDisableDelete) {
+            return false;
+        }
+
+        return chip.isSelected() &&
+                ((mAvatarPosition == AVATAR_POSITION_END && offset == getChipEnd(chip)) ||
+                (mAvatarPosition != AVATAR_POSITION_END && offset == getChipStart(chip)));
+    }
+
+    /**
+     * Remove the chip and any text associated with it from the RecipientEditTextView.
+     */
+    // Visible for testing.
+    /* package */void removeChip(DrawableRecipientChip chip) {
+        Spannable spannable = getSpannable();
+        int spanStart = spannable.getSpanStart(chip);
+        int spanEnd = spannable.getSpanEnd(chip);
+        Editable text = getText();
+        int toDelete = spanEnd;
+        boolean wasSelected = chip == mSelectedChip;
+        // Clear that there is a selected chip before updating any text.
+        if (wasSelected) {
+            mSelectedChip = null;
+        }
+        // Always remove trailing spaces when removing a chip.
+        while (toDelete >= 0 && toDelete < text.length() && text.charAt(toDelete) == ' ') {
+            toDelete++;
+        }
+        spannable.removeSpan(chip);
+        if (spanStart >= 0 && toDelete > 0) {
+            text.delete(spanStart, toDelete);
+        }
+        if (wasSelected) {
+            clearSelectedChip();
+        }
+    }
+
+    /**
+     * Replace this currently selected chip with a new chip
+     * that uses the contact data provided.
+     */
+    // Visible for testing.
+    /*package*/ void replaceChip(DrawableRecipientChip chip, RecipientEntry entry) {
+        boolean wasSelected = chip == mSelectedChip;
+        if (wasSelected) {
+            mSelectedChip = null;
+        }
+        int start = getChipStart(chip);
+        int end = getChipEnd(chip);
+        getSpannable().removeSpan(chip);
+        Editable editable = getText();
+        CharSequence chipText = createChip(entry, false);
+        if (chipText != null) {
+            if (start == -1 || end == -1) {
+                Log.e(TAG, "The chip to replace does not exist but should.");
+                editable.insert(0, chipText);
+            } else {
+                if (!TextUtils.isEmpty(chipText)) {
+                    // There may be a space to replace with this chip's new
+                    // associated space. Check for it
+                    int toReplace = end;
+                    while (toReplace >= 0 && toReplace < editable.length()
+                            && editable.charAt(toReplace) == ' ') {
+                        toReplace++;
+                    }
+                    editable.replace(start, toReplace, chipText);
+                }
+            }
+        }
+        setCursorVisible(true);
+        if (wasSelected) {
+            clearSelectedChip();
+        }
+    }
+
+    /**
+     * Handle click events for a chip. When a selected chip receives a click
+     * event, see if that event was in the delete icon. If so, delete it.
+     * Otherwise, unselect the chip.
+     */
+    public void onClick(DrawableRecipientChip chip, int offset, float x, float y) {
+        if (chip.isSelected()) {
+            if (isInDelete(chip, offset, x, y)) {
+                removeChip(chip);
+            } else {
+                clearSelectedChip();
+            }
+        }
+    }
+
+    private boolean chipsPending() {
+        return mPendingChipsCount > 0 || (mRemovedSpans != null && mRemovedSpans.size() > 0);
+    }
+
+    @Override
+    public void removeTextChangedListener(TextWatcher watcher) {
+        mTextWatcher = null;
+        super.removeTextChangedListener(watcher);
+    }
+
+    private class RecipientTextWatcher implements TextWatcher {
+
+        @Override
+        public void afterTextChanged(Editable s) {
+            // If the text has been set to null or empty, make sure we remove
+            // all the spans we applied.
+            if (TextUtils.isEmpty(s)) {
+                // Remove all the chips spans.
+                Spannable spannable = getSpannable();
+                DrawableRecipientChip[] chips = spannable.getSpans(0, getText().length(),
+                        DrawableRecipientChip.class);
+                for (DrawableRecipientChip chip : chips) {
+                    spannable.removeSpan(chip);
+                }
+                if (mMoreChip != null) {
+                    spannable.removeSpan(mMoreChip);
+                }
+                clearSelectedChip();
+                return;
+            }
+            // Get whether there are any recipients pending addition to the
+            // view. If there are, don't do anything in the text watcher.
+            if (chipsPending()) {
+                return;
+            }
+            // If the user is editing a chip, don't clear it.
+            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;
+                if (end != len) {
+                    last = s.charAt(end);
+                } else {
+                    last = s.charAt(len);
+                }
+                if (last == COMMIT_CHAR_SPACE) {
+                    if (!isPhoneQuery()) {
+                        // Check if this is a valid email address. If it is,
+                        // commit it.
+                        String text = getText().toString();
+                        int tokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
+                        String sub = text.substring(tokenStart, mTokenizer.findTokenEnd(text,
+                                tokenStart));
+                        if (!TextUtils.isEmpty(sub) && mValidator != null &&
+                                mValidator.isValid(sub)) {
+                            commitByCharacter();
+                        }
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+            // 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 == 1) {
+                // If the item deleted is a space, and the thing before the
+                // space is a chip, delete the entire span.
+                int selStart = getSelectionStart();
+                DrawableRecipientChip[] repl = getSpannable().getSpans(selStart, selStart,
+                        DrawableRecipientChip.class);
+                if (repl.length > 0) {
+                    // There is a chip there! Just remove it.
+                    Editable editable = getText();
+                    // Add the separator token.
+                    int tokenStart = mTokenizer.findTokenStart(editable, selStart);
+                    int tokenEnd = mTokenizer.findTokenEnd(editable, tokenStart);
+                    tokenEnd = tokenEnd + 1;
+                    if (tokenEnd > editable.length()) {
+                        tokenEnd = editable.length();
+                    }
+                    editable.delete(tokenStart, tokenEnd);
+                    getSpannable().removeSpan(repl[0]);
+                }
+            } else if (count > before) {
+                if (mSelectedChip != null
+                    && isGeneratedContact(mSelectedChip)) {
+                    if (lastCharacterIsCommitCharacter(s)) {
+                        commitByCharacter();
+                        return;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+            // Do nothing.
+        }
+    }
+
+   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}.
+     */
+    private void handlePasteClip(ClipData clip) {
+        removeTextChangedListener(mTextWatcher);
+
+        if (clip != null && clip.getDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)){
+            for (int i = 0; i < clip.getItemCount(); i++) {
+                CharSequence paste = clip.getItemAt(i).getText();
+                if (paste != null) {
+                    int start = getSelectionStart();
+                    int end = getSelectionEnd();
+                    Editable editable = getText();
+                    if (start >= 0 && end >= 0 && start != end) {
+                        editable.append(paste, start, end);
+                    } else {
+                        editable.insert(end, paste);
+                    }
+                    handlePasteAndReplace();
+                }
+            }
+        }
+
+        mHandler.post(mAddTextWatcher);
+    }
+
+    @Override
+    public boolean onTextContextMenuItem(int id) {
+        if (id == android.R.id.paste) {
+            ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
+                    Context.CLIPBOARD_SERVICE);
+            handlePasteClip(clipboard.getPrimaryClip());
+            return true;
+        }
+        return super.onTextContextMenuItem(id);
+    }
+
+    private void handlePasteAndReplace() {
+        ArrayList<DrawableRecipientChip> created = handlePaste();
+        if (created != null && created.size() > 0) {
+            // Perform reverse lookups on the pasted contacts.
+            IndividualReplacementTask replace = new IndividualReplacementTask();
+            replace.execute(created);
+        }
+    }
+
+    // Visible for testing.
+    /* package */ArrayList<DrawableRecipientChip> handlePaste() {
+        String text = getText().toString();
+        int originalTokenStart = mTokenizer.findTokenStart(text, getSelectionEnd());
+        String lastAddress = text.substring(originalTokenStart);
+        int tokenStart = originalTokenStart;
+        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 && tokenStart != prevTokenStart) {
+                prevTokenStart = tokenStart;
+                tokenStart = mTokenizer.findTokenStart(text, tokenStart);
+                findChip = findChip(tokenStart);
+                if (tokenStart == originalTokenStart && findChip == null) {
+                    break;
+                }
+            }
+            if (tokenStart != originalTokenStart) {
+                if (findChip != null) {
+                    tokenStart = prevTokenStart;
+                }
+                int tokenEnd;
+                DrawableRecipientChip createdChip;
+                while (tokenStart < originalTokenStart) {
+                    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);
+                }
+            }
+        }
+        // Take a look at the last token. If the token has been completed with a
+        // commit character, create a chip.
+        if (isCompletedToken(lastAddress)) {
+            Editable editable = getText();
+            tokenStart = editable.toString().indexOf(lastAddress, originalTokenStart);
+            commitChip(tokenStart, editable.length(), editable);
+            created.add(findChip(tokenStart));
+        }
+        return created;
+    }
+
+    // Visible for testing.
+    /* package */int movePastTerminators(int tokenEnd) {
+        if (tokenEnd >= length()) {
+            return tokenEnd;
+        }
+        char atEnd = getText().toString().charAt(tokenEnd);
+        if (atEnd == COMMIT_CHAR_COMMA || atEnd == COMMIT_CHAR_SEMICOLON) {
+            tokenEnd++;
+        }
+        // This token had not only an end token character, but also a space
+        // separating it from the next token.
+        if (tokenEnd < length() && getText().toString().charAt(tokenEnd) == ' ') {
+            tokenEnd++;
+        }
+        return tokenEnd;
+    }
+
+    private class RecipientReplacementTask extends AsyncTask<Void, Void, Void> {
+        private DrawableRecipientChip createFreeChip(RecipientEntry entry) {
+            try {
+                if (mNoChips) {
+                    return null;
+                }
+                return constructChipSpan(entry, false,
+                        false /*leave space for contact icon */);
+            } catch (NullPointerException e) {
+                Log.e(TAG, e.getMessage(), e);
+                return null;
+            }
+        }
+
+        @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);
+            }
+            // 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<DrawableRecipientChip> recipients =
+                    new ArrayList<DrawableRecipientChip>();
+            DrawableRecipientChip[] existingChips = getSortedRecipients();
+            for (int i = 0; i < existingChips.length; i++) {
+                recipients.add(existingChips[i]);
+            }
+            if (mRemovedSpans != null) {
+                recipients.addAll(mRemovedSpans);
+            }
+            ArrayList<String> addresses = new ArrayList<String>();
+            DrawableRecipientChip chip;
+            for (int i = 0; i < recipients.size(); i++) {
+                chip = recipients.get(i);
+                if (chip != null) {
+                    addresses.add(createAddressText(chip.getEntry()));
+                }
+            }
+            final BaseRecipientAdapter adapter = getAdapter();
+            RecipientAlternatesAdapter.getMatchingRecipients(getContext(), adapter, addresses,
+                    adapter.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) {
+                final Runnable runnable = new Runnable() {
+                    @Override
+                    public void run() {
+                        final Editable text = new SpannableStringBuilder(getText());
+                        int i = 0;
+                        for (final DrawableRecipientChip chip : recipients) {
+                            final 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.
+                                    final int start = text.getSpanStart(chip);
+                                    if (start != -1) {
+                                        // Replacing the entirety of what the chip represented,
+                                        // including the extra space dividing it from other chips.
+                                        final int end =
+                                                Math.min(text.getSpanEnd(chip) + 1, text.length());
+                                        text.removeSpan(chip);
+                                        // Make sure we always have just 1 space at the end to
+                                        // separate this chip from the next chip.
+                                        final 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.
+                                        text.replace(start, end, displayText);
+                                        replacement.setOriginalText(displayText.toString());
+                                        replacements.set(i, null);
+
+                                        recipients.set(i, replacement);
+                                    }
+                                }
+                            }
+                            i++;
+                        }
+                        setText(text);
+                    }
+                };
+
+                if (Looper.myLooper() == Looper.getMainLooper()) {
+                    runnable.run();
+                } else {
+                    mHandler.post(runnable);
+                }
+            }
+        }
+    }
+
+    private class IndividualReplacementTask
+            extends AsyncTask<ArrayList<DrawableRecipientChip>, Void, Void> {
+        @Override
+        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<DrawableRecipientChip> originalRecipients = params[0];
+            ArrayList<String> addresses = new ArrayList<String>();
+            DrawableRecipientChip chip;
+            for (int i = 0; i < originalRecipients.size(); i++) {
+                chip = originalRecipients.get(i);
+                if (chip != null) {
+                    addresses.add(createAddressText(chip.getEntry()));
+                }
+            }
+            final BaseRecipientAdapter adapter = getAdapter();
+            RecipientAlternatesAdapter.getMatchingRecipients(getContext(), adapter, addresses,
+                    adapter.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.
+                                    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);
+                                            }
+                                        });
+                                    }
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void matchesNotFound(final Set<String> unfoundAddresses) {
+                            // No action required
+                        }
+                    });
+            return null;
+        }
+    }
+
+
+    /**
+     * MoreImageSpan is a simple class created for tracking the existence of a
+     * more chip across activity restarts/
+     */
+    private class MoreImageSpan extends ImageSpan {
+        public MoreImageSpan(Drawable b) {
+            super(b);
+        }
+    }
+
+    @Override
+    public boolean onDown(MotionEvent e) {
+        return false;
+    }
+
+    @Override
+    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+        // Do nothing.
+        return false;
+    }
+
+    @Override
+    public void onLongPress(MotionEvent event) {
+        if (mSelectedChip != null) {
+            return;
+        }
+        float x = event.getX();
+        float y = event.getY();
+        final int offset = putOffsetInRange(x, y);
+        DrawableRecipientChip currentChip = findChip(offset);
+        if (currentChip != null) {
+            if (mDragEnabled) {
+                // Start drag-and-drop for the selected chip.
+                startDrag(currentChip);
+            } else {
+                // Copy the selected chip email address.
+                showCopyDialog(currentChip.getEntry().getDestination());
+            }
+        }
+    }
+
+    // The following methods are used to provide some functionality on older versions of Android
+    // These methods were copied out of JB MR2's TextView
+    /////////////////////////////////////////////////
+    private int supportGetOffsetForPosition(float x, float y) {
+        if (getLayout() == null) return -1;
+        final int line = supportGetLineAtCoordinate(y);
+        final int offset = supportGetOffsetAtCoordinate(line, x);
+        return offset;
+    }
+
+    private float supportConvertToLocalHorizontalCoordinate(float x) {
+        x -= getTotalPaddingLeft();
+        // Clamp the position to inside of the view.
+        x = Math.max(0.0f, x);
+        x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
+        x += getScrollX();
+        return x;
+    }
+
+    private int supportGetLineAtCoordinate(float y) {
+        y -= getTotalPaddingLeft();
+        // Clamp the position to inside of the view.
+        y = Math.max(0.0f, y);
+        y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
+        y += getScrollY();
+        return getLayout().getLineForVertical((int) y);
+    }
+
+    private int supportGetOffsetAtCoordinate(int line, float x) {
+        x = supportConvertToLocalHorizontalCoordinate(x);
+        return getLayout().getOffsetForHorizontal(line, x);
+    }
+    /////////////////////////////////////////////////
+
+    /**
+     * Enables drag-and-drop for chips.
+     */
+    public void enableDrag() {
+        mDragEnabled = true;
+    }
+
+    /**
+     * Starts drag-and-drop for the selected chip.
+     */
+    private void startDrag(DrawableRecipientChip currentChip) {
+        String address = currentChip.getEntry().getDestination();
+        ClipData data = ClipData.newPlainText(address, address + COMMIT_CHAR_COMMA);
+
+        // Start drag mode.
+        startDrag(data, new RecipientChipShadow(currentChip), null, 0);
+
+        // Remove the current chip, so drag-and-drop will result in a move.
+        // TODO (phamm): consider readd this chip if it's dropped outside a target.
+        removeChip(currentChip);
+    }
+
+    /**
+     * Handles drag event.
+     */
+    @Override
+    public boolean onDragEvent(DragEvent event) {
+        switch (event.getAction()) {
+            case DragEvent.ACTION_DRAG_STARTED:
+                // Only handle plain text drag and drop.
+                return event.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
+            case DragEvent.ACTION_DRAG_ENTERED:
+                requestFocus();
+                return true;
+            case DragEvent.ACTION_DROP:
+                handlePasteClip(event.getClipData());
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Drag shadow for a {@link RecipientChip}.
+     */
+    private final class RecipientChipShadow extends DragShadowBuilder {
+        private final DrawableRecipientChip mChip;
+
+        public RecipientChipShadow(DrawableRecipientChip chip) {
+            mChip = chip;
+        }
+
+        @Override
+        public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
+            Rect rect = mChip.getBounds();
+            shadowSize.set(rect.width(), rect.height());
+            shadowTouchPoint.set(rect.centerX(), rect.centerY());
+        }
+
+        @Override
+        public void onDrawShadow(Canvas canvas) {
+            mChip.draw(canvas);
+        }
+    }
+
+    private void showCopyDialog(final String address) {
+        if (!mAttachedToWindow) {
+            return;
+        }
+        mCopyAddress = address;
+        mCopyDialog.setTitle(address);
+        mCopyDialog.setContentView(R.layout.copy_chip_dialog_layout);
+        mCopyDialog.setCancelable(true);
+        mCopyDialog.setCanceledOnTouchOutside(true);
+        Button button = (Button)mCopyDialog.findViewById(android.R.id.button1);
+        button.setOnClickListener(this);
+        int btnTitleId;
+        if (isPhoneQuery()) {
+            btnTitleId = R.string.copy_number;
+        } else {
+            btnTitleId = R.string.copy_email;
+        }
+        String buttonTitle = getContext().getResources().getString(btnTitleId);
+        button.setText(buttonTitle);
+        mCopyDialog.setOnDismissListener(this);
+        mCopyDialog.show();
+    }
+
+    @Override
+    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+        // Do nothing.
+        return false;
+    }
+
+    @Override
+    public void onShowPress(MotionEvent e) {
+        // Do nothing.
+    }
+
+    @Override
+    public boolean onSingleTapUp(MotionEvent e) {
+        // Do nothing.
+        return false;
+    }
+
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        mCopyAddress = null;
+    }
+
+    @Override
+    public void onClick(View v) {
+        // Copy this to the clipboard.
+        ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(
+                Context.CLIPBOARD_SERVICE);
+        clipboard.setPrimaryClip(ClipData.newPlainText("", mCopyAddress));
+        mCopyDialog.dismiss();
+    }
+
+    protected boolean isPhoneQuery() {
+        return getAdapter() != null
+                && getAdapter().getQueryType() == BaseRecipientAdapter.QUERY_TYPE_PHONE;
+    }
+
+    @Override
+    public BaseRecipientAdapter getAdapter() {
+        return (BaseRecipientAdapter) super.getAdapter();
+    }
+}
diff --git a/src/com/android/ex/chips/RecipientEntry.java b/src/com/android/ex/chips/RecipientEntry.java
new file mode 100644
index 0000000..7d9b87f
--- /dev/null
+++ b/src/com/android/ex/chips/RecipientEntry.java
@@ -0,0 +1,256 @@
+/*
+ * 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.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.
+ */
+public class RecipientEntry {
+    /* package */ static final int INVALID_CONTACT = -1;
+    /**
+     * A GENERATED_CONTACT is one that was created based entirely on
+     * information passed in to the RecipientEntry from an external source
+     * that is not a real contact.
+     */
+    /* package */ static final int GENERATED_CONTACT = -2;
+
+    /** Used when {@link #mDestinationType} is invalid and thus shouldn't be used for display. */
+    /* package */ static final int INVALID_DESTINATION_TYPE = -1;
+
+    public static final int ENTRY_TYPE_PERSON = 0;
+
+    public static final int ENTRY_TYPE_SIZE = 1;
+
+    private final int mEntryType;
+
+    /**
+     * True when this entry is the first entry in a group, which should have a photo and display
+     * name, while the second or later entries won't.
+     */
+    private boolean mIsFirstLevel;
+    private final String mDisplayName;
+
+    /** Destination for this contact entry. Would be an email address or a phone number. */
+    private final String mDestination;
+    /** Type of the destination like {@link Email#TYPE_HOME} */
+    private final int mDestinationType;
+    /**
+     * Label of the destination which will be used when type was {@link Email#TYPE_CUSTOM}.
+     * Can be null when {@link #mDestinationType} is {@link #INVALID_DESTINATION_TYPE}.
+     */
+    private final String mDestinationLabel;
+    /** ID for the person */
+    private final long mContactId;
+    /** ID for the directory this contact came from, or <code>null</code> */
+    private final Long mDirectoryId;
+    /** ID for the destination */
+    private final long mDataId;
+    private final boolean mIsDivider;
+
+    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;
+
+    /** See {@link ContactsContract.Contacts#LOOKUP_KEY} */
+    private final String mLookupKey;
+
+    private RecipientEntry(int entryType, String displayName, String destination,
+            int destinationType, String destinationLabel, long contactId, Long directoryId,
+            long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid,
+            String lookupKey) {
+        mEntryType = entryType;
+        mIsFirstLevel = isFirstLevel;
+        mDisplayName = displayName;
+        mDestination = destination;
+        mDestinationType = destinationType;
+        mDestinationLabel = destinationLabel;
+        mContactId = contactId;
+        mDirectoryId = directoryId;
+        mDataId = dataId;
+        mPhotoThumbnailUri = photoThumbnailUri;
+        mPhotoBytes = null;
+        mIsDivider = false;
+        mIsValid = isValid;
+        mLookupKey = lookupKey;
+    }
+
+    public boolean isValid() {
+        return mIsValid;
+    }
+
+    /**
+     * Determine if this was a RecipientEntry created from recipient info or
+     * an entry from contacts.
+     */
+    public static boolean isCreatedRecipient(long id) {
+        return id == RecipientEntry.INVALID_CONTACT || id == RecipientEntry.GENERATED_CONTACT;
+    }
+
+    /**
+     * Construct a RecipientEntry from just an address that has been entered.
+     * This address has not been resolved to a contact and therefore does not
+     * have a contact id or photo.
+     */
+    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, null /* directoryId */,
+                INVALID_CONTACT, null, true, isValid, null /* lookupKey */);
+    }
+
+    /**
+     * 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, null /* directoryId */,
+                INVALID_CONTACT, null, true, isValid, null /* lookupKey */);
+    }
+
+    /**
+     * @return the display name for the entry.  If the display name source is larger than
+     * {@link DisplayNameSources#PHONE} we use the contact's display name, but if not,
+     * i.e. the display name came from an email address or a phone number, we don't use it
+     * to avoid confusion and just use the destination instead.
+     */
+    private static String pickDisplayName(int displayNameSource, String displayName,
+            String destination) {
+        return (displayNameSource > DisplayNameSources.PHONE) ? displayName : destination;
+    }
+
+    /**
+     * Construct a RecipientEntry from just an address that has been entered
+     * 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,
+            boolean isValid) {
+        return new RecipientEntry(ENTRY_TYPE_PERSON, display, address, INVALID_DESTINATION_TYPE,
+                null, GENERATED_CONTACT, null /* directoryId */, GENERATED_CONTACT, null, true,
+                isValid, null /* lookupKey */);
+    }
+
+    public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
+            String destination, int destinationType, String destinationLabel, long contactId,
+            Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid,
+            String lookupKey) {
+        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
+                displayName, destination), destination, destinationType, destinationLabel,
+                contactId, directoryId, dataId, photoThumbnailUri, true, isValid, lookupKey);
+    }
+
+    public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
+            String destination, int destinationType, String destinationLabel, long contactId,
+            Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid,
+            String lookupKey) {
+        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
+                displayName, destination), destination, destinationType, destinationLabel,
+                contactId, directoryId, dataId, (thumbnailUriAsString != null
+                        ? Uri.parse(thumbnailUriAsString) : null), true, isValid, lookupKey);
+    }
+
+    public static RecipientEntry constructSecondLevelEntry(String displayName,
+            int displayNameSource, String destination, int destinationType,
+            String destinationLabel, long contactId, Long directoryId, long dataId,
+            String thumbnailUriAsString, boolean isValid, String lookupKey) {
+        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
+                displayName, destination), destination, destinationType, destinationLabel,
+                contactId, directoryId, dataId, (thumbnailUriAsString != null
+                        ? Uri.parse(thumbnailUriAsString) : null), false, isValid, lookupKey);
+    }
+
+    public int getEntryType() {
+        return mEntryType;
+    }
+
+    public String getDisplayName() {
+        return mDisplayName;
+    }
+
+    public String getDestination() {
+        return mDestination;
+    }
+
+    public int getDestinationType() {
+        return mDestinationType;
+    }
+
+    public String getDestinationLabel() {
+        return mDestinationLabel;
+    }
+
+    public long getContactId() {
+        return mContactId;
+    }
+
+    public Long getDirectoryId() {
+        return mDirectoryId;
+    }
+
+    public long getDataId() {
+        return mDataId;
+    }
+
+    public boolean isFirstLevel() {
+        return mIsFirstLevel;
+    }
+
+    public Uri getPhotoThumbnailUri() {
+        return mPhotoThumbnailUri;
+    }
+
+    /** This can be called outside main Looper thread. */
+    public synchronized void setPhotoBytes(byte[] photoBytes) {
+        mPhotoBytes = photoBytes;
+    }
+
+    /** This can be called outside main Looper thread. */
+    public synchronized byte[] getPhotoBytes() {
+        return mPhotoBytes;
+    }
+
+    public boolean isSeparator() {
+        return mIsDivider;
+    }
+
+    public boolean isSelectable() {
+        return mEntryType == ENTRY_TYPE_PERSON;
+    }
+
+    public String getLookupKey() {
+        return mLookupKey;
+    }
+
+    @Override
+    public String toString() {
+        return mDisplayName + " <" + mDestination + ">, isValid=" + mIsValid;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/ex/chips/SingleRecipientArrayAdapter.java b/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
new file mode 100644
index 0000000..985953f
--- /dev/null
+++ b/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
@@ -0,0 +1,43 @@
+/*
+ * 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.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
+
+class SingleRecipientArrayAdapter extends ArrayAdapter<RecipientEntry> {
+    private final DropdownChipLayouter mDropdownChipLayouter;
+
+    public SingleRecipientArrayAdapter(Context context, RecipientEntry entry,
+            DropdownChipLayouter dropdownChipLayouter) {
+        super(context, dropdownChipLayouter.getAlternateItemLayoutResId(), new RecipientEntry[] {
+            entry
+        });
+
+        mDropdownChipLayouter = dropdownChipLayouter;
+    }
+
+    @Override
+    public View getView(int position, View convertView, ViewGroup parent) {
+        return mDropdownChipLayouter.bindView(convertView, parent, getItem(position), position,
+                AdapterType.SINGLE_RECIPIENT, null);
+    }
+}
diff --git a/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java b/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java
new file mode 100644
index 0000000..8012b5c
--- /dev/null
+++ b/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java
@@ -0,0 +1,83 @@
+/*
+ * 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 directory id of the contact associated with this chip.
+     */
+    Long getDirectoryId();
+
+    /**
+     * Get the directory lookup key associated with this chip, or <code>null</code>.
+     */
+    String getLookupKey();
+
+    /**
+     * 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/src/com/android/ex/chips/recipientchip/DrawableRecipientChip.java b/src/com/android/ex/chips/recipientchip/DrawableRecipientChip.java
new file mode 100644
index 0000000..396a8ac
--- /dev/null
+++ b/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/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java b/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java
new file mode 100644
index 0000000..455f2cb
--- /dev/null
+++ b/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java
@@ -0,0 +1,115 @@
+/*
+ * 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 getDirectoryId() {
+        return mDelegate.getDirectoryId();
+    }
+
+    @Override
+    public String getLookupKey() {
+        return mDelegate.getLookupKey();
+    }
+
+    @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/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java b/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java
new file mode 100644
index 0000000..533f53f
--- /dev/null
+++ b/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java
@@ -0,0 +1,115 @@
+/*
+ * 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 mDirectoryId;
+
+    private final String mLookupKey;
+
+    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();
+        mDirectoryId = entry.getDirectoryId();
+        mLookupKey = entry.getLookupKey();
+        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 getDirectoryId() {
+        return mDirectoryId;
+    }
+
+    @Override
+    public String getLookupKey() {
+        return mLookupKey;
+    }
+
+    @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/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java b/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java
new file mode 100644
index 0000000..6d3d27d
--- /dev/null
+++ b/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java
@@ -0,0 +1,114 @@
+/*
+ * 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) {
+        this(drawable, entry, DynamicDrawableSpan.ALIGN_BOTTOM);
+    }
+
+    public VisibleRecipientChip(final Drawable drawable, final RecipientEntry entry,
+            final int verticalAlignment) {
+        super(drawable, verticalAlignment);
+
+        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 getDirectoryId() {
+        return mDelegate.getDirectoryId();
+    }
+
+    @Override
+    public String getLookupKey() {
+        return mDelegate.getLookupKey();
+    }
+
+    @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/tests/Android.mk b/tests/Android.mk
new file mode 100644
index 0000000..b01aa0c
--- /dev/null
+++ b/tests/Android.mk
@@ -0,0 +1,29 @@
+# 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.
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := ChipsTests
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_CERTIFICATE := platform
+LOCAL_STATIC_JAVA_LIBRARIES += android-common-chips
+LOCAL_RESOURCE_DIR := frameworks/ex/chips/res/
+LOCAL_AAPT_FLAGS := --auto-add-overlay
+LOCAL_AAPT_FLAGS += --extra-packages com.android.ex.chips
+
+include $(BUILD_PACKAGE)
+
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
new file mode 100644
index 0000000..b2b307e
--- /dev/null
+++ b/tests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.ex.chips.tests"
+        android:sharedUserId="com.android.uid.test">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!-- Run tests with "runtest android-common" -->
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+            android:targetPackage="com.android.ex.chips.tests"
+            android:label="Chips Tests" />
+
+</manifest>
diff --git a/tests/src/com/android/ex/chips/ChipsTest.java b/tests/src/com/android/ex/chips/ChipsTest.java
new file mode 100644
index 0000000..9116e26
--- /dev/null
+++ b/tests/src/com/android/ex/chips/ChipsTest.java
@@ -0,0 +1,1028 @@
+/*
+ * 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.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.Editable;
+import android.text.SpannableStringBuilder;
+import android.text.style.ImageSpan;
+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 DrawableRecipientChip[] mMockRecips;
+
+    private RecipientEntry[] mMockEntries;
+
+    private Rfc822Tokenizer mTokenizer;
+
+    private Editable mEditable;
+
+    class BaseMockRecipientEditTextView extends RecipientEditTextView {
+
+        public BaseMockRecipientEditTextView(Context context) {
+            super(context, null);
+            mTokenizer = new Rfc822Tokenizer();
+            setTokenizer(mTokenizer);
+        }
+
+        @Override
+        public DrawableRecipientChip[] getSortedRecipients() {
+            return mMockRecips;
+        }
+
+        @Override
+        public int getLineHeight() {
+            return 48;
+        }
+
+        @Override
+        Drawable getChipBackground(RecipientEntry contact) {
+            return createChipBackground();
+        }
+
+        @Override
+        public int getViewWidth() {
+            return 100;
+        }
+    }
+
+    class MockRecipientEditTextView extends BaseMockRecipientEditTextView {
+
+        public MockRecipientEditTextView(Context context) {
+            super(context);
+            mTokenizer = new Rfc822Tokenizer();
+            setTokenizer(mTokenizer);
+        }
+
+        @Override
+        public DrawableRecipientChip[] getSortedRecipients() {
+            return mMockRecips;
+        }
+
+        @Override
+        public Editable getText() {
+            return mEditable;
+        }
+
+        @Override
+        public Editable getSpannable() {
+            return mEditable;
+        }
+
+        @Override
+        public int getLineHeight() {
+            return 48;
+        }
+
+        @Override
+        Drawable getChipBackground(RecipientEntry contact) {
+            return createChipBackground();
+        }
+
+        @Override
+        public int length() {
+            return mEditable != null ? mEditable.length() : 0;
+        }
+
+        @Override
+        public String toString() {
+            return mEditable != null ? mEditable.toString() : "";
+        }
+
+        @Override
+        public int getViewWidth() {
+            return 100;
+        }
+    }
+
+    private class TestBaseRecipientAdapter extends BaseRecipientAdapter {
+        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() {
+        mEditable = new SpannableStringBuilder();
+        MockRecipientEditTextView view = new MockRecipientEditTextView(getContext());
+        view.setAdapter(new TestBaseRecipientAdapter(getContext()));
+        return view;
+    }
+
+    public void testCreateDisplayText() {
+        RecipientEditTextView view = createViewForTesting();
+        RecipientEntry entry = RecipientEntry.constructGeneratedEntry("User Name, Jr",
+                "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, ", true);
+        testAddress = view.createAddressText(alreadyFormatted);
+        testDisplay = view.createChipDisplayText(alreadyFormatted);
+        assertEquals("Expected a properly formatted RFC email address", "<user@username.com>, ",
+                testAddress);
+        assertEquals("Expected a displayable name", "user@username.com", testDisplay);
+
+        RecipientEntry alreadyFormattedNoSpace = RecipientEntry
+                .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>", true);
+        testAddress = view.createAddressText(alreadyNamed);
+        testDisplay = view.createChipDisplayText(alreadyNamed);
+        assertEquals(
+                "Expected address that used the name not the excess address name",
+                "User Name <user@username.com>, ", testAddress);
+        assertEquals("Expected a displayable name", "User Name", testDisplay);
+    }
+
+    public void testSanitizeBetween() {
+        // First, add 2 chips and then make sure we remove
+        // the extra content between them correctly.
+        populateMocks(2);
+        MockRecipientEditTextView view = createViewForTesting();
+        String first = (String) mTokenizer.terminateToken("FIRST");
+        String second = (String) mTokenizer.terminateToken("SECOND");
+        String extra = "EXTRA";
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first + extra + second);
+        int firstStart = mEditable.toString().indexOf(first);
+        int firstEnd = firstStart + first.trim().length();
+        int secondStart = mEditable.toString().indexOf(second);
+        int secondEnd = secondStart + second.trim().length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], secondStart, secondEnd, 0);
+        view.sanitizeBetween();
+        String editableString = mEditable.toString();
+        assertEquals(editableString.indexOf(extra), -1);
+        assertEquals(editableString.indexOf(first), firstStart);
+        assertEquals(editableString.indexOf(second), secondStart - extra.length());
+        assertEquals(editableString, (first + second));
+
+        // Add 1 chip and make sure that we remove the extra stuff before it correctly.
+        mEditable = new SpannableStringBuilder();
+        populateMocks(1);
+        mEditable.append(extra);
+        mEditable.append(first);
+        firstStart = mEditable.toString().indexOf(first);
+        firstEnd = firstStart + first.length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], firstStart, firstEnd, 0);
+        view.sanitizeBetween();
+        assertEquals(mEditable.toString(), first);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), firstStart
+                - extra.length());
+    }
+
+    public void testSanitizeEnd() {
+        // First, add 2 chips and then make sure we remove
+        // the extra content between them correctly.
+        populateMocks(2);
+        MockRecipientEditTextView view = createViewForTesting();
+        String first = (String) mTokenizer.terminateToken("FIRST");
+        String second = (String) mTokenizer.terminateToken("SECOND");
+        String extra = "EXTRA";
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first + second);
+        int firstStart = mEditable.toString().indexOf(first);
+        int firstEnd = firstStart + first.trim().length();
+        int secondStart = mEditable.toString().indexOf(second);
+        int secondEnd = secondStart + second.trim().length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], secondStart, secondEnd, 0);
+        view.sanitizeEnd();
+        String editableString = mEditable.toString();
+        assertEquals(editableString.indexOf(extra), -1);
+        assertEquals(editableString.indexOf(first), firstStart);
+        assertEquals(editableString.indexOf(second), secondStart);
+        assertEquals(editableString, (first + second));
+        mEditable.append(extra);
+        editableString = mEditable.toString();
+        assertEquals(mEditable.toString(), (first + second + extra));
+        view.sanitizeEnd();
+        assertEquals(mEditable.toString(), (first + second));
+    }
+
+    public void testMoreChipPlainText() {
+        MockRecipientEditTextView view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        String first = (String) mTokenizer.terminateToken("FIRST");
+        String second = (String) mTokenizer.terminateToken("SECOND");
+        String third = (String) mTokenizer.terminateToken("THIRD");
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first+second+third);
+        int thirdStart = mEditable.toString().indexOf(third);
+        int thirdEnd = thirdStart + third.trim().length();
+        view.createMoreChipPlainText();
+        ImageSpan moreChip = view.getMoreChip();
+        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
+        assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd + 1);
+    }
+
+    public void testCountTokens() {
+        MockRecipientEditTextView view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        String first = (String) mTokenizer.terminateToken("FIRST");
+        String second = (String) mTokenizer.terminateToken("SECOND");
+        String third = (String) mTokenizer.terminateToken("THIRD");
+        String fourth = "FOURTH,";
+        String fifth = "FIFTH,";
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first+second+third+fourth+fifth);
+        assertEquals(view.countTokens(mEditable), 5);
+    }
+
+    public void testTooManyRecips() {
+        BaseMockRecipientEditTextView view = new BaseMockRecipientEditTextView(getContext());
+        view.setMoreItem(createTestMoreItem());
+        for (int i = 0; i < 100; i++) {
+            view.append(mTokenizer.terminateToken(i + ""));
+        }
+        assertEquals(view.countTokens(view.getText()), 100);
+        view.handlePendingChips();
+        view.createMoreChip();
+        ImageSpan moreChip = view.getMoreChip();
+        // We show 2 chips then place a more chip.
+        int secondStart = view.getText().toString().indexOf(
+                (String) mTokenizer.terminateToken(RecipientEditTextView.CHIP_LIMIT + ""));
+        assertEquals(view.getText().getSpanStart(moreChip), secondStart);
+        assertEquals(view.getText().getSpanEnd(moreChip), view.length());
+        assertEquals(view.getSortedRecipients(), null);
+    }
+
+    public void testMoreChip() {
+        // Add 3 chips: this is the trigger point at which the more chip will be created.
+        // Test that adding the chips and then creating and removing the more chip, as if
+        // the user were focusing/ removing focus from the chips field.
+        populateMocks(3);
+        MockRecipientEditTextView view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        String first = (String) mTokenizer.terminateToken("FIRST");
+        String second = (String) mTokenizer.terminateToken("SECOND");
+        String third = (String) mTokenizer.terminateToken("THIRD");
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first+second+third);
+
+        int firstStart = mEditable.toString().indexOf(first);
+        int firstEnd = firstStart + first.trim().length();
+        int secondStart = mEditable.toString().indexOf(second);
+        int secondEnd = secondStart + second.trim().length();
+        int thirdStart = mEditable.toString().indexOf(third);
+        int thirdEnd = thirdStart + third.trim().length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
+
+        view.createMoreChip();
+        assertEquals(mEditable.toString(), first+second+third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
+        // Find the more chip.
+        ImageSpan moreChip = view.getMoreChip();
+        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
+        assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd + 1);
+
+        view.removeMoreChip();
+        assertEquals(mEditable.toString(), first+second+third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdEnd);
+        moreChip = view.getMoreChip();
+        assertEquals(mEditable.getSpanStart(moreChip), -1);
+
+        // Rinse and repeat, just in case!
+        view.createMoreChip();
+        assertEquals(mEditable.toString(), first+second+third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
+        // Find the more chip.
+        moreChip = view.getMoreChip();
+        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
+        assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd + 1);
+
+        view.removeMoreChip();
+        assertEquals(mEditable.toString(), first+second+third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdEnd);
+        moreChip = view.getMoreChip();
+        assertEquals(mEditable.getSpanStart(moreChip), -1);
+    }
+
+    public void testMoreChipLotsOfUsers() {
+        // Test adding and removing the more chip in the case where we have a lot of users.
+        populateMocks(10);
+        MockRecipientEditTextView view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        String first = (String) mTokenizer.terminateToken("FIRST");
+        String second = (String) mTokenizer.terminateToken("SECOND");
+        String third = (String) mTokenizer.terminateToken("THIRD");
+        String fourth = (String) mTokenizer.terminateToken("FOURTH");
+        String fifth = (String) mTokenizer.terminateToken("FIFTH");
+        String sixth = (String) mTokenizer.terminateToken("SIXTH");
+        String seventh = (String) mTokenizer.terminateToken("SEVENTH");
+        String eigth = (String) mTokenizer.terminateToken("EIGHTH");
+        String ninth = (String) mTokenizer.terminateToken("NINTH");
+        String tenth = (String) mTokenizer.terminateToken("TENTH");
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first+second+third+fourth+fifth+sixth+seventh+eigth+ninth+tenth);
+
+        int firstStart = mEditable.toString().indexOf(first);
+        int firstEnd = firstStart + first.trim().length();
+        int secondStart = mEditable.toString().indexOf(second);
+        int secondEnd = secondStart + second.trim().length();
+        int thirdStart = mEditable.toString().indexOf(third);
+        int thirdEnd = thirdStart + third.trim().length();
+        int fourthStart = mEditable.toString().indexOf(fourth);
+        int fourthEnd = fourthStart + fourth.trim().length();
+        int fifthStart = mEditable.toString().indexOf(fifth);
+        int fifthEnd = fifthStart + fifth.trim().length();
+        int sixthStart = mEditable.toString().indexOf(sixth);
+        int sixthEnd = sixthStart + sixth.trim().length();
+        int seventhStart = mEditable.toString().indexOf(seventh);
+        int seventhEnd = seventhStart + seventh.trim().length();
+        int eighthStart = mEditable.toString().indexOf(eigth);
+        int eighthEnd = eighthStart + eigth.trim().length();
+        int ninthStart = mEditable.toString().indexOf(ninth);
+        int ninthEnd = ninthStart + ninth.trim().length();
+        int tenthStart = mEditable.toString().indexOf(tenth);
+        int tenthEnd = tenthStart + tenth.trim().length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 10], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 9], secondStart, secondEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 8], thirdStart, thirdEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 7], fourthStart, fourthEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 6], fifthStart, fifthEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 5], sixthStart, sixthEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 4], seventhStart, seventhEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], eighthStart, eighthEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], ninthStart, ninthEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], tenthStart, tenthEnd, 0);
+
+        view.createMoreChip();
+        assertEquals(mEditable.toString(), first + second + third + fourth + fifth + sixth
+                + seventh + eigth + ninth + tenth);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 10]), firstStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 9]), secondStart);
+        // Find the more chip.
+        ImageSpan moreChip = view.getMoreChip();
+        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
+        assertEquals(mEditable.getSpanEnd(moreChip), tenthEnd + 1);
+
+        view.removeMoreChip();
+        assertEquals(mEditable.toString(), first + second + third + fourth + fifth + sixth
+                + seventh + eigth + ninth + tenth);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 10]), firstStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 9]), secondStart);
+
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 8]), thirdStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 7]), fourthStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 6]), fifthStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 5]), sixthStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 4]), seventhStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), eighthStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), ninthStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), tenthStart);
+        moreChip = view.getMoreChip();
+        assertEquals(mEditable.getSpanStart(moreChip), -1);
+
+    }
+
+    public void testMoreChipSpecialChars() {
+        // Make sure the more chip correctly handles extra tokenizer characters in the middle
+        // of chip text.
+        populateMocks(3);
+        MockRecipientEditTextView view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        String first = (String) mTokenizer.terminateToken("FI,RST");
+        String second = (String) mTokenizer.terminateToken("SE,COND");
+        String third = (String) mTokenizer.terminateToken("THI,RD");
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first+second+third);
+
+        int firstStart = mEditable.toString().indexOf(first);
+        int firstEnd = firstStart + first.trim().length();
+        int secondStart = mEditable.toString().indexOf(second);
+        int secondEnd = secondStart + second.trim().length();
+        int thirdStart = mEditable.toString().indexOf(third);
+        int thirdEnd = thirdStart + third.trim().length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
+
+        view.createMoreChip();
+        assertEquals(mEditable.toString(), first+second+third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
+        // Find the more chip.
+        ImageSpan moreChip = view.getMoreChip();
+        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
+        assertEquals(mEditable.getSpanEnd(moreChip), thirdEnd + 1);
+
+        view.removeMoreChip();
+        assertEquals(mEditable.toString(), first+second+third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdEnd);
+        moreChip = view.getMoreChip();
+        assertEquals(mEditable.getSpanStart(moreChip), -1);
+    }
+
+    public void testMoreChipDupes() {
+        // Make sure the more chip is correctly added and removed when we have duplicate chips.
+        populateMocks(4);
+        MockRecipientEditTextView view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        String first = (String) mTokenizer.terminateToken("FIRST");
+        String second = (String) mTokenizer.terminateToken("SECOND");
+        String third = (String) mTokenizer.terminateToken("THIRD");
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first+second+third+third);
+
+        int firstStart = mEditable.toString().indexOf(first);
+        int firstEnd = firstStart + first.trim().length();
+        int secondStart = mEditable.toString().indexOf(second);
+        int secondEnd = secondStart + second.trim().length();
+        int thirdStart = mEditable.toString().indexOf(third);
+        int thirdEnd = thirdStart + third.trim().length();
+        int thirdNextStart = mEditable.toString().indexOf(third, thirdEnd);
+        int thirdNextEnd = thirdNextStart + third.trim().length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 4], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], secondStart, secondEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], thirdStart, thirdEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdNextStart, thirdNextEnd, 0);
+
+        view.createMoreChip();
+        assertEquals(mEditable.toString(), first+second+third+third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 4]), firstStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), secondStart);
+        // Find the more chip.
+        ImageSpan moreChip = view.getMoreChip();
+        assertEquals(mEditable.getSpanStart(moreChip), thirdStart);
+        assertEquals(mEditable.getSpanEnd(moreChip), thirdNextEnd + 1);
+
+        view.removeMoreChip();
+        assertEquals(mEditable.toString(), first+second+third+third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 4]), firstStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 4]), firstEnd);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), secondStart);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), thirdStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), thirdEnd);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), thirdNextStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), thirdNextEnd);
+        moreChip = view.getMoreChip();
+        assertEquals(mEditable.getSpanStart(moreChip), -1);
+    }
+
+    public void testRemoveChip() {
+        // Create 3 chips to start and test removing chips in various postions.
+        populateMocks(3);
+        MockRecipientEditTextView view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        String first = (String) mTokenizer.terminateToken("FIRST");
+        String second = (String) mTokenizer.terminateToken("SECOND");
+        String third = (String) mTokenizer.terminateToken("THIRD");
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first + second + third);
+
+        int firstStart = mEditable.toString().indexOf(first);
+        int firstEnd = firstStart + first.length();
+        int secondStart = mEditable.toString().indexOf(second);
+        int secondEnd = secondStart + second.length();
+        int thirdStart = mEditable.toString().indexOf(third);
+        int thirdEnd = thirdStart + third.length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
+        assertEquals(mEditable.toString(), first + second + third);
+        // Test removing the middle chip.
+        view.removeChip(mMockRecips[mMockRecips.length - 2]);
+        assertEquals(mEditable.toString(), first + third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), -1);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), -1);
+        int newThirdStart = mEditable.toString().indexOf(third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), newThirdStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), newThirdStart
+                + third.length());
+
+        // Test removing the first chip.
+        populateMocks(3);
+        view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first + second + third);
+
+        firstStart = mEditable.toString().indexOf(first);
+        firstEnd = firstStart + first.length();
+        secondStart = mEditable.toString().indexOf(second);
+        secondEnd = secondStart + second.length();
+        thirdStart = mEditable.toString().indexOf(third);
+        thirdEnd = thirdStart + third.length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
+        assertEquals(mEditable.toString(), first + second + third);
+        view.removeChip(mMockRecips[mMockRecips.length - 3]);
+        assertEquals(mEditable.toString(), second + third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), -1);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), -1);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), 0);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), second.length());
+        newThirdStart = mEditable.toString().indexOf(third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), newThirdStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), newThirdStart
+                + third.length());
+
+        // Test removing the last chip.
+        populateMocks(3);
+        view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first + second + third);
+
+        firstStart = mEditable.toString().indexOf(first);
+        firstEnd = firstStart + first.length();
+        secondStart = mEditable.toString().indexOf(second);
+        secondEnd = secondStart + second.length();
+        thirdStart = mEditable.toString().indexOf(third);
+        thirdEnd = thirdStart + third.length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
+        assertEquals(mEditable.toString(), first + second + third);
+        view.removeChip(mMockRecips[mMockRecips.length - 1]);
+        assertEquals(mEditable.toString(), first + second);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), secondEnd);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), -1);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), -1);
+    }
+
+    public void testReplaceChip() {
+        populateMocks(3);
+        MockRecipientEditTextView view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        view.setChipBackground(createChipBackground());
+        view.setChipHeight(48);
+        String first = (String) mTokenizer.terminateToken("FIRST");
+        String second = (String) mTokenizer.terminateToken("SECOND");
+        String third = (String) mTokenizer.terminateToken("THIRD");
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first + second + third);
+
+        // Test replacing the first chip with a new chip.
+        int firstStart = mEditable.toString().indexOf(first);
+        int firstEnd = firstStart + first.trim().length();
+        int secondStart = mEditable.toString().indexOf(second);
+        int secondEnd = secondStart + second.trim().length();
+        int thirdStart = mEditable.toString().indexOf(third);
+        int thirdEnd = thirdStart + third.trim().length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
+        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", true));
+        assertEquals(mEditable.toString(), mTokenizer
+                .terminateToken("replacement <replacement@replacement.com>")
+                + second + third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), -1);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), -1);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), mEditable
+                .toString().indexOf(second));
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), mEditable
+                .toString().indexOf(second)
+                + second.trim().length());
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), mEditable
+                .toString().indexOf(third));
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), mEditable
+                .toString().indexOf(third)
+                + third.trim().length());
+        DrawableRecipientChip[] spans =
+                mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class);
+        assertEquals(spans.length, 3);
+        spans = mEditable
+                .getSpans(0, mEditable.toString().indexOf(second) - 1, DrawableRecipientChip.class);
+        assertEquals((String) spans[0].getDisplay(), "replacement");
+
+
+        // Test replacing the middle chip with a new chip.
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first + second + third);
+        firstStart = mEditable.toString().indexOf(first);
+        firstEnd = firstStart + first.trim().length();
+        secondStart = mEditable.toString().indexOf(second);
+        secondEnd = secondStart + second.trim().length();
+        thirdStart = mEditable.toString().indexOf(third);
+        thirdEnd = thirdStart + third.trim().length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
+        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", true));
+        assertEquals(mEditable.toString(), first + mTokenizer
+                .terminateToken("replacement <replacement@replacement.com>") + third);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), -1);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 2]), -1);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 1]), mEditable
+                .toString().indexOf(third));
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 1]), mEditable
+                .toString().indexOf(third)
+                + third.trim().length());
+        spans = mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class);
+        assertEquals(spans.length, 3);
+        spans = mEditable.getSpans(firstEnd, mEditable.toString().indexOf(third) - 1,
+                DrawableRecipientChip.class);
+        assertEquals((String) spans[0].getDisplay(), "replacement");
+
+
+        // Test replacing the last chip with a new chip.
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first + second + third);
+        firstStart = mEditable.toString().indexOf(first);
+        firstEnd = firstStart + first.trim().length();
+        secondStart = mEditable.toString().indexOf(second);
+        secondEnd = secondStart + second.trim().length();
+        thirdStart = mEditable.toString().indexOf(third);
+        thirdEnd = thirdStart + third.trim().length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
+        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", true));
+        assertEquals(mEditable.toString(), first + second + mTokenizer
+                .terminateToken("replacement <replacement@replacement.com>"));
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 3]), firstStart);
+        assertEquals(mEditable.getSpanEnd(mMockRecips[mMockRecips.length - 3]), firstEnd);
+        assertEquals(mEditable.getSpanStart(mMockRecips[mMockRecips.length - 2]), secondStart);
+        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(), DrawableRecipientChip.class);
+        assertEquals(spans.length, 3);
+        spans = mEditable
+                .getSpans(secondEnd, mEditable.length(), DrawableRecipientChip.class);
+        assertEquals((String) spans[0].getDisplay(), "replacement");
+    }
+
+    public void testHandlePaste() {
+        // Start with an empty edit field.
+        // Add an address; the text should be left as is.
+        MockRecipientEditTextView view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        view.setChipBackground(createChipBackground());
+        view.setChipHeight(48);
+        mEditable = new SpannableStringBuilder();
+        mEditable.append("user@user.com");
+        view.setSelection(mEditable.length());
+        view.handlePaste();
+        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
+        // the end of it. The address should stay as text.
+        mEditable = new SpannableStringBuilder();
+        String tokenizedUser = "user@user.com" + " ";
+        mEditable.append(tokenizedUser);
+        view.setSelection(mEditable.length());
+        view.handlePaste();
+        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
+        // the end of it. The address should become a chip
+        mEditable = new SpannableStringBuilder();
+        tokenizedUser = "user@user.com;";
+        mEditable.append(tokenizedUser);
+        view.setSelection(mEditable.length());
+        view.handlePaste();
+        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
+        // text.
+        mEditable = new SpannableStringBuilder();
+        mEditable.append("user1,user2@user.com");
+        view.setSelection(mEditable.length());
+        view.handlePaste();
+        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 1);
+        assertEquals(mEditable.getSpans(0, mEditable.toString().indexOf("user2@user.com"),
+                DrawableRecipientChip.class).length, 1);
+        assertEquals(mEditable.toString(), "<user1>, user2@user.com");
+
+        // Test adding a single address to the end of existing chips. The existing
+        // chips should remain, and the last address should stay as text.
+        populateMocks(3);
+        String first = (String) mTokenizer.terminateToken("FIRST");
+        String second = (String) mTokenizer.terminateToken("SECOND");
+        String third = (String) mTokenizer.terminateToken("THIRD");
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first + second + third);
+        view.setSelection(mEditable.length());
+        int firstStart = mEditable.toString().indexOf(first);
+        int firstEnd = firstStart + first.trim().length();
+        int secondStart = mEditable.toString().indexOf(second);
+        int secondEnd = secondStart + second.trim().length();
+        int thirdStart = mEditable.toString().indexOf(third);
+        int thirdEnd = thirdStart + third.trim().length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
+
+        mEditable.append("user@user.com");
+        view.setSelection(mEditable.length());
+        view.handlePaste();
+        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length,
+                mMockRecips.length);
+        assertEquals(mEditable.toString(), first + second + third + "user@user.com");
+
+        // Paste 2 addresses after existing chips. We expect the first address to be turned into
+        // a chip and the second to be left as text.
+        populateMocks(3);
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first + second + third);
+
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
+
+        mEditable.append("user1, user2@user.com");
+        view.setSelection(mEditable.length());
+        view.handlePaste();
+        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, DrawableRecipientChip.class).length, 1);
+        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
+                .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
+        // a chip and the second to be left as text. This removes the space seperator char between
+        // addresses.
+        populateMocks(3);
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first + second + third);
+
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
+
+        mEditable.append("user1,user2@user.com");
+        view.setSelection(mEditable.length());
+        view.handlePaste();
+        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, DrawableRecipientChip.class).length, 1);
+        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
+                .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.
+        mEditable = new SpannableStringBuilder();
+        mEditable.append("user1, user2@user.com,");
+        view.setSelection(mEditable.length());
+        view.handlePaste();
+        assertEquals(mEditable.getSpans(0, mEditable.length(), DrawableRecipientChip.class).length, 2);
+        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("<user1>"), mEditable
+                .toString().indexOf("user2@user.com") - 1, DrawableRecipientChip.class).length, 1);
+        assertEquals(mEditable.getSpans(mEditable.toString().indexOf("user2@user.com"), mEditable
+                .length(), DrawableRecipientChip.class).length, 1);
+        assertEquals(mEditable.toString(), "<user1>, <user2@user.com>, ");
+    }
+
+    public void testGetPastTerminators() {
+        MockRecipientEditTextView view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        view.setChipBackground(createChipBackground());
+        view.setChipHeight(48);
+        String test = "test";
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(test);
+        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
+                test.length());
+
+        test = "test,";
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(test);
+        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
+                test.length());
+
+        test = "test, ";
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(test);
+        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
+                test.length());
+
+        test = "test;";
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(test);
+        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
+                test.length());
+
+        test = "test; ";
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(test);
+        assertEquals(view.movePastTerminators(mTokenizer.findTokenEnd(mEditable.toString(), 0)),
+                test.length());
+    }
+
+    public void testIsCompletedToken() {
+        MockRecipientEditTextView view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        view.setChipBackground(createChipBackground());
+        view.setChipHeight(48);
+        assertTrue(view.isCompletedToken("test;"));
+        assertTrue(view.isCompletedToken("test,"));
+        assertFalse(view.isCompletedToken("test"));
+        assertFalse(view.isCompletedToken("test "));
+    }
+
+    public void testGetLastChip() {
+        populateMocks(3);
+        MockRecipientEditTextView view = createViewForTesting();
+        view.setMoreItem(createTestMoreItem());
+        view.setChipBackground(createChipBackground());
+        view.setChipHeight(48);
+        String first = (String) mTokenizer.terminateToken("FIRST");
+        String second = (String) mTokenizer.terminateToken("SECOND");
+        String third = (String) mTokenizer.terminateToken("THIRD");
+        mEditable = new SpannableStringBuilder();
+        mEditable.append(first + second + third);
+
+        // Test replacing the first chip with a new chip.
+        int firstStart = mEditable.toString().indexOf(first);
+        int firstEnd = firstStart + first.trim().length();
+        int secondStart = mEditable.toString().indexOf(second);
+        int secondEnd = secondStart + second.trim().length();
+        int thirdStart = mEditable.toString().indexOf(third);
+        int thirdEnd = thirdStart + third.trim().length();
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 3], firstStart, firstEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 2], secondStart, secondEnd, 0);
+        mEditable.setSpan(mMockRecips[mMockRecips.length - 1], thirdStart, thirdEnd, 0);
+        assertEquals(view.getLastChip(), mMockRecips[mMockRecips.length - 1]);
+        mEditable.append("extra");
+        assertEquals(view.getLastChip(), mMockRecips[mMockRecips.length - 1]);
+    }
+
+    private Drawable createChipBackground() {
+        Bitmap drawable = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+        return new BitmapDrawable(getContext().getResources(), drawable);
+    }
+
+    private TextView createTestMoreItem() {
+        TextView view = new TextView(getContext());
+        view.setText("<xliff:g id='count'>%1$s</xliff:g> more...");
+        return view;
+    }
+
+    private void populateMocks(int size) {
+        mMockEntries = new RecipientEntry[size];
+        for (int i = 0; i < size; i++) {
+            mMockEntries[i] = RecipientEntry.constructGeneratedEntry("user",
+                    "user@username.com", true);
+        }
+        mMockRecips = new DrawableRecipientChip[size];
+        for (int i = 0; i < size; 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/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java b/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
new file mode 100644
index 0000000..afb6a00
--- /dev/null
+++ b/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2012 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.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 testRemoveUndesiredDestinations() {
+        MatrixCursor c = new MatrixCursor(Queries.EMAIL.getProjection());
+        Cursor result;
+
+        // Test: Empty input
+        assertEquals(0, RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+                null /* desiredMimeType */, null /* lookupKey */).getCount());
+
+
+        // Test: One row
+        addRow(c, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
+
+        result = RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+                null /* desiredMimeType */, null /* lookupKey */);
+        assertEquals(1, result.getCount());
+        assertRow(result, 0, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
+
+        // Test: two unique rows, different destinations
+        addRow(c, "a", "2@android.com", 1, "home", 1000, 2000, "x", 0);
+
+        result = RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+                null /* desiredMimeType */, null /* lookupKey */);
+        assertEquals(2, result.getCount());
+        assertRow(result, 0, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
+        assertRow(result, 1, "a", "2@android.com", 1, "home", 1000, 2000, "x", 0);
+
+        // Test: add a third row with a non-unique destination.
+        addRow(c, "ax", "1@android.com", 11, "homex", 10001, 2000, "xx", 1);
+
+        // Third row should be removed.
+        result = RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+                null /* desiredMimeType */, null /* lookupKey */);
+        assertEquals(2, result.getCount());
+        assertRow(result, 0, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
+        assertRow(result, 1, "a", "2@android.com", 1, "home", 1000, 2000, "x", 0);
+
+        // Test: add a forth row with a non-unique destination again.
+        addRow(c, "ax", "2@android.com", 11, "homex", 10001, 2000, "xx", 1);
+
+        // Forth row should also be removed.
+        result = RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+                null /* desiredMimeType */, null /* lookupKey */);
+        assertEquals(2, result.getCount());
+        assertRow(result, 0, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
+        assertRow(result, 1, "a", "2@android.com", 1, "home", 1000, 2000, "x", 0);
+    }
+
+    private static MatrixCursor addRow(MatrixCursor c,
+            String displayName,
+            String destination,
+            int destinationType,
+            String destinationLabel,
+            long contactId,
+            long dataId,
+            String photoUri,
+            int displayNameSource
+            ) {
+        c.addRow(new Object[] {displayName, destination, destinationType, destinationLabel,
+                contactId, dataId, photoUri, displayNameSource});
+        return c;
+    }
+
+    private static void assertRow(Cursor c, int position,
+            String displayName,
+            String destination,
+            int destinationType,
+            String destinationLabel,
+            long contactId,
+            long dataId,
+            String photoUri,
+            int displayNameSource
+            ) {
+        assertTrue(c.moveToPosition(position));
+        assertEquals(displayName, c.getString(0));
+        assertEquals(destination, c.getString(1));
+        assertEquals(destinationType, c.getInt(2));
+        assertEquals(destinationLabel, c.getString(3));
+        assertEquals(contactId, c.getLong(4));
+        assertEquals(dataId, c.getLong(5));
+        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, null /* directoryId */, 0, (Uri) null,
+                            true, null /* lookupKey */);
+            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, null /* directoryId */, 0, (Uri) null,
+                            true, null /* lookupKey */);
+            final RecipientEntry entry2 =
+                    RecipientEntry.constructTopLevelEntry("2@android.com", DisplayNameSources.EMAIL,
+                            "2@android.com", 0, null, 0, null /* directoryId */, 0, (Uri) null,
+                            true, null /* lookupKey */);
+
+            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, null /* directoryId */, 0,
+                            Uri.parse("http://www.android.com"), true, null /* lookupKey */);
+            final RecipientEntry entry2 =
+                    RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.EMAIL,
+                            "2@android.com", 0, null, 0, null /* directoryId */,
+                            0, (Uri) null, true, null /* lookupKey */);
+
+            assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
+            assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry2, entry1), entry1);
+        }
+    }
+}