am 7a21dec1: (-s ours) Import translations. DO NOT MERGE

* commit '7a21dec19ae7fe9c3f7856cda12b0499ba2a1ccc':
  Import translations. DO NOT MERGE
diff --git a/carousel/test/res/values-az-rAZ/strings.xml b/carousel/test/res/values-az-rAZ/strings.xml
deleted file mode 100644
index 634f926..0000000
--- a/carousel/test/res/values-az-rAZ/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"Carousel istifadəsini göstərmək üçün tətbiq"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"Son Tətbiqlər"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"Son tapşırıqlar yoxdur"</string>
-</resources>
diff --git a/carousel/test/res/values-az/strings.xml b/carousel/test/res/values-az/strings.xml
deleted file mode 100644
index 634f926..0000000
--- a/carousel/test/res/values-az/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"Carousel istifadəsini göstərmək üçün tətbiq"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"Son Tətbiqlər"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"Son tapşırıqlar yoxdur"</string>
-</resources>
diff --git a/carousel/test/res/values-be/strings.xml b/carousel/test/res/values-be/strings.xml
deleted file mode 100644
index 631a815..0000000
--- a/carousel/test/res/values-be/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"Прыкладанне, якое адлюстроўвае выкарыстанне Carousel"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"Апошнія прыкладанні"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"Няма апошніх задач"</string>
-</resources>
diff --git a/carousel/test/res/values-en-rIN/strings.xml b/carousel/test/res/values-en-rIN/strings.xml
deleted file mode 100644
index 86ea2cb..0000000
--- a/carousel/test/res/values-en-rIN/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"Music Carousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"Carousel Test"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"An application to show the use of Carousel"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"Task Switcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"Recent Applications"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"No recent tasks"</string>
-</resources>
diff --git a/carousel/test/res/values-et-rEE/strings.xml b/carousel/test/res/values-et-rEE/strings.xml
deleted file mode 100644
index 59f0b5c..0000000
--- a/carousel/test/res/values-et-rEE/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"Rakendus, mis näitab karusselli kasutust"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"Hiljutised rakendused"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"Hiljutised ülesanded puuduvad"</string>
-</resources>
diff --git a/carousel/test/res/values-fr-rCA/strings.xml b/carousel/test/res/values-fr-rCA/strings.xml
deleted file mode 100644
index 87aeb34..0000000
--- a/carousel/test/res/values-fr-rCA/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"Une application expliquant l\'utilisation de Carousel"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"Applications récentes"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"Aucune tâche récente"</string>
-</resources>
diff --git a/carousel/test/res/values-hy-rAM/strings.xml b/carousel/test/res/values-hy-rAM/strings.xml
deleted file mode 100644
index 39bf205..0000000
--- a/carousel/test/res/values-hy-rAM/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"Հավելված` Carousel-ի օգտագործումը ցույց տալու համար"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"Վերջին հավելվածները"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"Վերջին առաջադրանքներ չկան"</string>
-</resources>
diff --git a/carousel/test/res/values-iw/strings.xml b/carousel/test/res/values-iw/strings.xml
index a4163c5..53fa5d2 100644
--- a/carousel/test/res/values-iw/strings.xml
+++ b/carousel/test/res/values-iw/strings.xml
@@ -21,8 +21,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
     <string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"אפליקציה להצגת השימוש בקרוסלה"</string>
+    <string name="carousel_test_activity_description" msgid="1632693812604375483">"יישום להצגת השימוש בקרוסלה"</string>
     <string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"אפליקציות אחרונות"</string>
+    <string name="recent_tasks_title" msgid="1030287226205477117">"יישומים אחרונים"</string>
     <string name="no_recent_tasks" msgid="6884096266670555780">"אין משימות אחרונות"</string>
 </resources>
diff --git a/carousel/test/res/values-ka-rGE/strings.xml b/carousel/test/res/values-ka-rGE/strings.xml
deleted file mode 100644
index 0764f3f..0000000
--- a/carousel/test/res/values-ka-rGE/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"Carousel-ის გამოყენების მაჩვენებელი აპლიკაცია"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"ბოლოდროინდელი აპლიკაციები"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"ბოლოდროინდელი ამოცანები არ არის."</string>
-</resources>
diff --git a/carousel/test/res/values-km-rKH/strings.xml b/carousel/test/res/values-km-rKH/strings.xml
deleted file mode 100644
index 5bb198d..0000000
--- a/carousel/test/res/values-km-rKH/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"កម្មវិធី​ត្រូវ​បង្ហាញ​ការ​ប្រើ Carousel"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"កម្មវិធី​ថ្មីៗ"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"គ្មាន​ភារកិច្ច​ថ្មីៗ"</string>
-</resources>
diff --git a/carousel/test/res/values-lo-rLA/strings.xml b/carousel/test/res/values-lo-rLA/strings.xml
deleted file mode 100644
index 83a4cbd..0000000
--- a/carousel/test/res/values-lo-rLA/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"ແອັບພລິເຄຊັນທີ່ໃຊ້ສະແດງປະໂຫຍດຂອງ Carousel"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"ແອັບຯພລິເຄຊັນທີ່ຫາກໍໃຊ້"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"ບໍ່ມີວຽກເມື່ອໄວໆນີ້ເທື່ອ"</string>
-</resources>
diff --git a/carousel/test/res/values-mn-rMN/strings.xml b/carousel/test/res/values-mn-rMN/strings.xml
deleted file mode 100644
index 20d9f8d..0000000
--- a/carousel/test/res/values-mn-rMN/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"Хөгжмийн тойруулга"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"Тойруулга тест"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"Тойруулга ашиглалтыг харуулах аппликешн"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"Даалгавар солигч"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"Сүүлийн аппликешн"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"Сүүлийн даалгавар хоосон"</string>
-</resources>
diff --git a/carousel/test/res/values-ms-rMY/strings.xml b/carousel/test/res/values-ms-rMY/strings.xml
deleted file mode 100644
index ab9381a..0000000
--- a/carousel/test/res/values-ms-rMY/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"Satu aplikasi untuk menunjukkan penggunaan Carousel"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"Aplikasi Terbaru"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"Tiada tugasan terbaru"</string>
-</resources>
diff --git a/carousel/test/res/values-nb/strings.xml b/carousel/test/res/values-nb/strings.xml
index 751baf9..8cddca0 100644
--- a/carousel/test/res/values-nb/strings.xml
+++ b/carousel/test/res/values-nb/strings.xml
@@ -21,8 +21,8 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
     <string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"En app som viser bruken av Carousel"</string>
+    <string name="carousel_test_activity_description" msgid="1632693812604375483">"En applikasjon som viser bruken av Carousel"</string>
     <string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"Nylige apper"</string>
+    <string name="recent_tasks_title" msgid="1030287226205477117">"Nylige applikasjoner"</string>
     <string name="no_recent_tasks" msgid="6884096266670555780">"Ingen nylige oppgaver"</string>
 </resources>
diff --git a/carousel/test/res/values-ne-rNP/strings.xml b/carousel/test/res/values-ne-rNP/strings.xml
deleted file mode 100644
index 838502f..0000000
--- a/carousel/test/res/values-ne-rNP/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"संगीत करउसेल"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"करउसेल परीक्षण"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"कारउसेलको प्रयोग देखाउन एउटा अनुप्रयोग"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"कार्य स्विच गर्ने"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"भर्खरैका अनुप्रयोगहरू"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"कुनै भरखरका कार्यहरू छैनन्।"</string>
-</resources>
diff --git a/carousel/test/res/values-ne/strings.xml b/carousel/test/res/values-ne/strings.xml
deleted file mode 100644
index 838502f..0000000
--- a/carousel/test/res/values-ne/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"संगीत करउसेल"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"करउसेल परीक्षण"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"कारउसेलको प्रयोग देखाउन एउटा अनुप्रयोग"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"कार्य स्विच गर्ने"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"भर्खरैका अनुप्रयोगहरू"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"कुनै भरखरका कार्यहरू छैनन्।"</string>
-</resources>
diff --git a/carousel/test/res/values-si-rLK/strings.xml b/carousel/test/res/values-si-rLK/strings.xml
deleted file mode 100644
index 5a1aa48..0000000
--- a/carousel/test/res/values-si-rLK/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"සංගීත Carousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"Carousel පරීක්ෂණය"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"Carousel භාවිතය පෙන්වීමට යෙදුමකි"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"කාර්ය ස්විචය"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"මෑත කාලීන යෙදුම්"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"මෑත කාර්යයන් නැත"</string>
-</resources>
diff --git a/carousel/test/res/values-si/strings.xml b/carousel/test/res/values-si/strings.xml
deleted file mode 100644
index 5a1aa48..0000000
--- a/carousel/test/res/values-si/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"සංගීත Carousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"Carousel පරීක්ෂණය"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"Carousel භාවිතය පෙන්වීමට යෙදුමකි"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"කාර්ය ස්විචය"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"මෑත කාලීන යෙදුම්"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"මෑත කාර්යයන් නැත"</string>
-</resources>
diff --git a/carousel/test/res/values-zh-rHK/strings.xml b/carousel/test/res/values-zh-rHK/strings.xml
deleted file mode 100644
index 193cb4a..0000000
--- a/carousel/test/res/values-zh-rHK/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-* Copyright (C) 2009 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="music_demo_activity_label" msgid="4382090808250495841">"MusicCarousel"</string>
-    <string name="carousel_test_activity_label" msgid="6014624482213318747">"CarouselTest"</string>
-    <string name="carousel_test_activity_description" msgid="1632693812604375483">"展示輪轉使用方法的應用程式"</string>
-    <string name="task_switcher_activity_label" msgid="714620143340933546">"TaskSwitcher"</string>
-    <string name="recent_tasks_title" msgid="1030287226205477117">"最近使用的應用程式"</string>
-    <string name="no_recent_tasks" msgid="6884096266670555780">"最近沒有任務"</string>
-</resources>
diff --git a/chips/Android.mk b/chips/Android.mk
index ef4b8ab..2bca597 100644
--- a/chips/Android.mk
+++ b/chips/Android.mk
@@ -17,7 +17,7 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE := android-common-chips
 LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4
-LOCAL_SDK_VERSION := 14
+LOCAL_SDK_VERSION := 19
 LOCAL_SRC_FILES := \
      $(call all-java-files-under, src) \
      $(call all-logtags-files-under, src)
diff --git a/chips/AndroidManifest.xml b/chips/AndroidManifest.xml
index 7487c18..02ea564 100644
--- a/chips/AndroidManifest.xml
+++ b/chips/AndroidManifest.xml
@@ -19,6 +19,6 @@
 
     <uses-sdk
         android:minSdkVersion="11"
-        android:targetSdkVersion="18" />
+        android:targetSdkVersion="19" />
 
 </manifest>
\ No newline at end of file
diff --git a/chips/project.properties b/chips/project.properties
index 1b8c5a3..91d2b02 100644
--- a/chips/project.properties
+++ b/chips/project.properties
@@ -11,5 +11,5 @@
 #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
 
 # Project target.
-target=android-18
+target=android-19
 android.library=true
diff --git a/chips/res/layout/chips_recipient_dropdown_item.xml b/chips/res/layout/chips_recipient_dropdown_item.xml
index cd11d67..b02b197 100644
--- a/chips/res/layout/chips_recipient_dropdown_item.xml
+++ b/chips/res/layout/chips_recipient_dropdown_item.xml
@@ -33,25 +33,25 @@
                   android:textSize="18sp"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
-                  android:paddingLeft="8dip"
                   android:singleLine="true"
-                  android:ellipsize="end" />
+                  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:paddingLeft="16dip"
                   android:singleLine="true"
                   android:ellipsize="end"
-                  android:layout_marginTop="-4dip" />
+                  android:layout_marginTop="-4dip"
+                  style="@style/ChipSubtitleStyle" />
     </LinearLayout>
     <ImageView
         android:id="@android:id/icon"
         android:layout_width="48dip"
         android:layout_height="48dip"
-        android:layout_marginLeft="12dip"
         android:src="@drawable/ic_contact_picture"
         android:cropToPadding="true"
-        android:scaleType="centerCrop" />
+        android:scaleType="centerCrop"
+        style="@style/ChipIconStyle" />
 </LinearLayout>
diff --git a/chips/res/values-az-rAZ/strings.xml b/chips/res/values-az-rAZ/strings.xml
deleted file mode 100644
index c1e8567..0000000
--- a/chips/res/values-az-rAZ/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-az/strings.xml b/chips/res/values-az/strings.xml
deleted file mode 100644
index c1e8567..0000000
--- a/chips/res/values-az/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-be/strings.xml b/chips/res/values-be/strings.xml
deleted file mode 100644
index e71f0e2..0000000
--- a/chips/res/values-be/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-en-rIN/strings.xml b/chips/res/values-en-rIN/strings.xml
deleted file mode 100644
index 1ae784b..0000000
--- a/chips/res/values-en-rIN/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-et-rEE/strings.xml b/chips/res/values-et-rEE/strings.xml
deleted file mode 100644
index f32e66d..0000000
--- a/chips/res/values-et-rEE/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-fr-rCA/strings.xml b/chips/res/values-fr-rCA/strings.xml
deleted file mode 100644
index 758f5a8..0000000
--- a/chips/res/values-fr-rCA/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-hy-rAM/strings.xml b/chips/res/values-hy-rAM/strings.xml
deleted file mode 100644
index ca2695c..0000000
--- a/chips/res/values-hy-rAM/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-ka-rGE/strings.xml b/chips/res/values-ka-rGE/strings.xml
deleted file mode 100644
index 9d24e05..0000000
--- a/chips/res/values-ka-rGE/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-km-rKH/strings.xml b/chips/res/values-km-rKH/strings.xml
deleted file mode 100644
index e51c667..0000000
--- a/chips/res/values-km-rKH/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-lo-rLA/strings.xml b/chips/res/values-lo-rLA/strings.xml
deleted file mode 100644
index 44912af..0000000
--- a/chips/res/values-lo-rLA/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-mn-rMN/strings.xml b/chips/res/values-mn-rMN/strings.xml
deleted file mode 100644
index 89923c3..0000000
--- a/chips/res/values-mn-rMN/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-ms-rMY/strings.xml b/chips/res/values-ms-rMY/strings.xml
deleted file mode 100644
index 76320f9..0000000
--- a/chips/res/values-ms-rMY/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-ne-rNP/strings.xml b/chips/res/values-ne-rNP/strings.xml
deleted file mode 100644
index 4710484..0000000
--- a/chips/res/values-ne-rNP/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-ne/strings.xml b/chips/res/values-ne/strings.xml
deleted file mode 100644
index 4710484..0000000
--- a/chips/res/values-ne/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-si-rLK/strings.xml b/chips/res/values-si-rLK/strings.xml
deleted file mode 100644
index 313405f..0000000
--- a/chips/res/values-si-rLK/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-si/strings.xml b/chips/res/values-si/strings.xml
deleted file mode 100644
index 313405f..0000000
--- a/chips/res/values-si/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values-sw600dp/styles.xml b/chips/res/values-sw600dp/styles.xml
index b6014ea..00988a9 100644
--- a/chips/res/values-sw600dp/styles.xml
+++ b/chips/res/values-sw600dp/styles.xml
@@ -13,7 +13,8 @@
      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">
+<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>
@@ -25,5 +26,7 @@
         <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/chips/res/values-v17/styles-v17.xml b/chips/res/values-v17/styles-v17.xml
new file mode 100644
index 0000000..d151a75
--- /dev/null
+++ b/chips/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/chips/res/values-zh-rHK/strings.xml b/chips/res/values-zh-rHK/strings.xml
deleted file mode 100644
index 31a122a..0000000
--- a/chips/res/values-zh-rHK/strings.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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/chips/res/values/attrs.xml b/chips/res/values/attrs.xml
index 44c2500..d3500aa 100644
--- a/chips/res/values/attrs.xml
+++ b/chips/res/values/attrs.xml
@@ -15,13 +15,21 @@
 -->
 <resources>
     <declare-styleable name="RecipientEditTextView">
-        <attr name="invalidChipBackground" format="reference" />
+        <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="chipAlternatesLayout" format="reference" />
-        <attr name="chipPadding" format="reference" />
-        <attr name="chipHeight" 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>
+</resources>
\ No newline at end of file
diff --git a/chips/res/values/dimen.xml b/chips/res/values/dimen.xml
index 98354d2..f989c86 100644
--- a/chips/res/values/dimen.xml
+++ b/chips/res/values/dimen.xml
@@ -20,4 +20,8 @@
     <dimen name="chip_text_size">14sp</dimen>
     <dimen name="line_spacing_extra">4dip</dimen>
     <integer name="chips_max_lines">-1</integer>
-</resources>
\ No newline at end of file
+
+    <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/chips/res/values/styles.xml b/chips/res/values/styles.xml
index 8e2abf9..9b60cde 100644
--- a/chips/res/values/styles.xml
+++ b/chips/res/values/styles.xml
@@ -13,7 +13,8 @@
      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">
+<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>
@@ -25,5 +26,19 @@
         <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/chips/sample/res/values-af/strings.xml b/chips/sample/res/values-af/strings.xml
index b29edff..e4c4945 100644
--- a/chips/sample/res/values-af/strings.xml
+++ b/chips/sample/res/values-af/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips-voorbeeld"</string>
     <string name="email_addresses" msgid="5320415175940315400">"E-posadresse"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Foonnommers"</string>
 </resources>
diff --git a/chips/sample/res/values-am/strings.xml b/chips/sample/res/values-am/strings.xml
index 1da685a..d19c4e8 100644
--- a/chips/sample/res/values-am/strings.xml
+++ b/chips/sample/res/values-am/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"የቺፕስ ናሙና"</string>
     <string name="email_addresses" msgid="5320415175940315400">"የኢሜይል አድራሻዎች"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"ስልክ ቁጥሮች"</string>
 </resources>
diff --git a/chips/sample/res/values-ar/strings.xml b/chips/sample/res/values-ar/strings.xml
index 09f9e76..4492ec7 100644
--- a/chips/sample/res/values-ar/strings.xml
+++ b/chips/sample/res/values-ar/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"عينة شرائح"</string>
     <string name="email_addresses" msgid="5320415175940315400">"عناوين البريد الإلكتروني"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"أرقام الهواتف"</string>
 </resources>
diff --git a/chips/sample/res/values-bg/strings.xml b/chips/sample/res/values-bg/strings.xml
index 24d690b..4c118c1 100644
--- a/chips/sample/res/values-bg/strings.xml
+++ b/chips/sample/res/values-bg/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Имейл адреси"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Телефонни номера"</string>
 </resources>
diff --git a/chips/sample/res/values-ca/strings.xml b/chips/sample/res/values-ca/strings.xml
index 054ee13..847cc6f 100644
--- a/chips/sample/res/values-ca/strings.xml
+++ b/chips/sample/res/values-ca/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Mostra de xips"</string>
     <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/chips/sample/res/values-cs/strings.xml b/chips/sample/res/values-cs/strings.xml
index fcdb9cf..3e0a928 100644
--- a/chips/sample/res/values-cs/strings.xml
+++ b/chips/sample/res/values-cs/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Vzorové čipy"</string>
     <string name="email_addresses" msgid="5320415175940315400">"E-mailové adresy"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Telefonní čísla"</string>
 </resources>
diff --git a/chips/sample/res/values-da/strings.xml b/chips/sample/res/values-da/strings.xml
index 41279d3..e55fcc6 100644
--- a/chips/sample/res/values-da/strings.xml
+++ b/chips/sample/res/values-da/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Eksempel på chips"</string>
     <string name="email_addresses" msgid="5320415175940315400">"E-mailadresser"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Telefonnumre"</string>
 </resources>
diff --git a/chips/sample/res/values-de/strings.xml b/chips/sample/res/values-de/strings.xml
index c234ec4..614081c 100644
--- a/chips/sample/res/values-de/strings.xml
+++ b/chips/sample/res/values-de/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"E-Mail-Adressen"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Telefonnummern"</string>
 </resources>
diff --git a/chips/sample/res/values-el/strings.xml b/chips/sample/res/values-el/strings.xml
index ea827d9..a90018a 100644
--- a/chips/sample/res/values-el/strings.xml
+++ b/chips/sample/res/values-el/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Δείγμα τσιπ"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Διευθύνσεις ηλεκτρονικού ταχυδρομείου"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Αριθμοί τηλεφώνου"</string>
 </resources>
diff --git a/chips/sample/res/values-en-rGB/strings.xml b/chips/sample/res/values-en-rGB/strings.xml
index 8cf71de..aaccb10 100644
--- a/chips/sample/res/values-en-rGB/strings.xml
+++ b/chips/sample/res/values-en-rGB/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Email Addresses"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Phone Numbers"</string>
 </resources>
diff --git a/chips/sample/res/values-en-rIN/strings.xml b/chips/sample/res/values-en-rIN/strings.xml
index 8cf71de..aaccb10 100644
--- a/chips/sample/res/values-en-rIN/strings.xml
+++ b/chips/sample/res/values-en-rIN/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Email Addresses"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Phone Numbers"</string>
 </resources>
diff --git a/chips/sample/res/values-es-rUS/strings.xml b/chips/sample/res/values-es-rUS/strings.xml
index 270a120..e314778 100644
--- a/chips/sample/res/values-es-rUS/strings.xml
+++ b/chips/sample/res/values-es-rUS/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Muestra de chips"</string>
     <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/chips/sample/res/values-es/strings.xml b/chips/sample/res/values-es/strings.xml
index 270a120..dd64514 100644
--- a/chips/sample/res/values-es/strings.xml
+++ b/chips/sample/res/values-es/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Muestra de Chips"</string>
     <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/chips/sample/res/values-et-rEE/strings.xml b/chips/sample/res/values-et-rEE/strings.xml
index e343cf4..5c7d6e5 100644
--- a/chips/sample/res/values-et-rEE/strings.xml
+++ b/chips/sample/res/values-et-rEE/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"E-posti aadressid"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Telefoninumbrid"</string>
 </resources>
diff --git a/chips/sample/res/values-fa/strings.xml b/chips/sample/res/values-fa/strings.xml
index d31e347..8ee4162 100644
--- a/chips/sample/res/values-fa/strings.xml
+++ b/chips/sample/res/values-fa/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"نمونه تراشه‌ها"</string>
     <string name="email_addresses" msgid="5320415175940315400">"آدرس‌های ایمیل"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"شماره‌ تلفن‌ها"</string>
 </resources>
diff --git a/chips/sample/res/values-fi/strings.xml b/chips/sample/res/values-fi/strings.xml
index 348473d..c72df4d 100644
--- a/chips/sample/res/values-fi/strings.xml
+++ b/chips/sample/res/values-fi/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Sähköpostiosoitteet"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Puhelinnumerot"</string>
 </resources>
diff --git a/chips/sample/res/values-fr-rCA/strings.xml b/chips/sample/res/values-fr-rCA/strings.xml
index 68dcf33..e88de2d 100644
--- a/chips/sample/res/values-fr-rCA/strings.xml
+++ b/chips/sample/res/values-fr-rCA/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Échantillon Chips"</string>
     <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/chips/sample/res/values-fr/strings.xml b/chips/sample/res/values-fr/strings.xml
index e0bf7ac..2b1c18e 100644
--- a/chips/sample/res/values-fr/strings.xml
+++ b/chips/sample/res/values-fr/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Échantillon Chips"</string>
     <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/chips/sample/res/values-hi/strings.xml b/chips/sample/res/values-hi/strings.xml
index 20a8435..bae6585 100644
--- a/chips/sample/res/values-hi/strings.xml
+++ b/chips/sample/res/values-hi/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"चिप्‍स नमूने"</string>
     <string name="email_addresses" msgid="5320415175940315400">"ईमेल पते"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"फ़ोन नंबर"</string>
 </resources>
diff --git a/chips/sample/res/values-hr/strings.xml b/chips/sample/res/values-hr/strings.xml
index d6da228..6eb8a8e 100644
--- a/chips/sample/res/values-hr/strings.xml
+++ b/chips/sample/res/values-hr/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"E-adrese"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Telefonski brojevi"</string>
 </resources>
diff --git a/chips/sample/res/values-hu/strings.xml b/chips/sample/res/values-hu/strings.xml
index b2ba000..1d00752 100644
--- a/chips/sample/res/values-hu/strings.xml
+++ b/chips/sample/res/values-hu/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"„Chips” minta"</string>
     <string name="email_addresses" msgid="5320415175940315400">"E-mail címek"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Telefonszámok"</string>
 </resources>
diff --git a/chips/sample/res/values-hy-rAM/strings.xml b/chips/sample/res/values-hy-rAM/strings.xml
index 09ad981..fbdcb21 100644
--- a/chips/sample/res/values-hy-rAM/strings.xml
+++ b/chips/sample/res/values-hy-rAM/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Չիպերի նմուշ"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Էլփոստի հասցեներ"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Հեռախոսահամարներ"</string>
 </resources>
diff --git a/chips/sample/res/values-in/strings.xml b/chips/sample/res/values-in/strings.xml
index 036e97d..1ebd148 100644
--- a/chips/sample/res/values-in/strings.xml
+++ b/chips/sample/res/values-in/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Contoh Chip"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Alamat Email"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Nomor Telepon"</string>
 </resources>
diff --git a/chips/sample/res/values-it/strings.xml b/chips/sample/res/values-it/strings.xml
index 67bca55..aefbd01 100644
--- a/chips/sample/res/values-it/strings.xml
+++ b/chips/sample/res/values-it/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Indirizzi email"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Numeri di telefono"</string>
 </resources>
diff --git a/chips/sample/res/values-iw/strings.xml b/chips/sample/res/values-iw/strings.xml
index 7ead2ae..24c7e69 100644
--- a/chips/sample/res/values-iw/strings.xml
+++ b/chips/sample/res/values-iw/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"דוגמאות שבבים"</string>
     <string name="email_addresses" msgid="5320415175940315400">"כתובות דוא\"ל"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"מספרי טלפון"</string>
 </resources>
diff --git a/chips/sample/res/values-ja/strings.xml b/chips/sample/res/values-ja/strings.xml
index 6b0c0a7..c75120a 100644
--- a/chips/sample/res/values-ja/strings.xml
+++ b/chips/sample/res/values-ja/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"チップサンプル"</string>
     <string name="email_addresses" msgid="5320415175940315400">"メールアドレス"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"電話番号"</string>
 </resources>
diff --git a/chips/sample/res/values-ka-rGE/strings.xml b/chips/sample/res/values-ka-rGE/strings.xml
index ec7f764..a21dab5 100644
--- a/chips/sample/res/values-ka-rGE/strings.xml
+++ b/chips/sample/res/values-ka-rGE/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"ჩიპების ნიმუში"</string>
     <string name="email_addresses" msgid="5320415175940315400">"ელფოსტის მისამართები"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"ტელეფონის ნომრები"</string>
 </resources>
diff --git a/chips/sample/res/values-km-rKH/strings.xml b/chips/sample/res/values-km-rKH/strings.xml
index 70ca37e..3730a7d 100644
--- a/chips/sample/res/values-km-rKH/strings.xml
+++ b/chips/sample/res/values-km-rKH/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"គំរូ​បន្ទះ​សៀគ្វី"</string>
     <string name="email_addresses" msgid="5320415175940315400">"អាសយដ្ឋាន​អ៊ីមែល"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"លេខទូរស័ព្ទ"</string>
 </resources>
diff --git a/chips/sample/res/values-ko/strings.xml b/chips/sample/res/values-ko/strings.xml
index 32b49a7..24d2793 100644
--- a/chips/sample/res/values-ko/strings.xml
+++ b/chips/sample/res/values-ko/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"칩 샘플"</string>
     <string name="email_addresses" msgid="5320415175940315400">"이메일 주소"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"전화번호"</string>
 </resources>
diff --git a/chips/sample/res/values-lo-rLA/strings.xml b/chips/sample/res/values-lo-rLA/strings.xml
index 6ccf492..6357807 100644
--- a/chips/sample/res/values-lo-rLA/strings.xml
+++ b/chips/sample/res/values-lo-rLA/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"ທີ່ຢູ່ອີເມວ"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"ເບີໂທລະສັບ:"</string>
 </resources>
diff --git a/chips/sample/res/values-lt/strings.xml b/chips/sample/res/values-lt/strings.xml
index ce73b40..b966062 100644
--- a/chips/sample/res/values-lt/strings.xml
+++ b/chips/sample/res/values-lt/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Lustų pavyzdžiai"</string>
     <string name="email_addresses" msgid="5320415175940315400">"El. pašto adresai"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Telefonų numeriai"</string>
 </resources>
diff --git a/chips/sample/res/values-lv/strings.xml b/chips/sample/res/values-lv/strings.xml
index 6dd6ffe..fec05b5 100644
--- a/chips/sample/res/values-lv/strings.xml
+++ b/chips/sample/res/values-lv/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <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/chips/sample/res/values-mn-rMN/strings.xml b/chips/sample/res/values-mn-rMN/strings.xml
index 1398a43..5289e5c 100644
--- a/chips/sample/res/values-mn-rMN/strings.xml
+++ b/chips/sample/res/values-mn-rMN/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Чипний дээж"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Имэйл хаягууд"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Утасны дугаарууд"</string>
 </resources>
diff --git a/chips/sample/res/values-ms-rMY/strings.xml b/chips/sample/res/values-ms-rMY/strings.xml
index 696871d..12ab807 100644
--- a/chips/sample/res/values-ms-rMY/strings.xml
+++ b/chips/sample/res/values-ms-rMY/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Sampel Cip"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Alamat E-mel"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Nombor Telefon"</string>
 </resources>
diff --git a/chips/sample/res/values-nb/strings.xml b/chips/sample/res/values-nb/strings.xml
index 2d5e56a..3bff3e2 100644
--- a/chips/sample/res/values-nb/strings.xml
+++ b/chips/sample/res/values-nb/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips-eksempel"</string>
     <string name="email_addresses" msgid="5320415175940315400">"E-postadresser"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Telefonnumre"</string>
 </resources>
diff --git a/chips/sample/res/values-nl/strings.xml b/chips/sample/res/values-nl/strings.xml
index f47ff3a..8951311 100644
--- a/chips/sample/res/values-nl/strings.xml
+++ b/chips/sample/res/values-nl/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chipsvoorbeeld"</string>
     <string name="email_addresses" msgid="5320415175940315400">"E-mailadressen"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Telefoonnummers"</string>
 </resources>
diff --git a/chips/sample/res/values-pl/strings.xml b/chips/sample/res/values-pl/strings.xml
index 573d22e..fedec0d 100644
--- a/chips/sample/res/values-pl/strings.xml
+++ b/chips/sample/res/values-pl/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Próbka chipsów"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Adresy e-mail"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Numery telefonów"</string>
 </resources>
diff --git a/chips/sample/res/values-pt-rPT/strings.xml b/chips/sample/res/values-pt-rPT/strings.xml
index 675e2eb..951d30a 100644
--- a/chips/sample/res/values-pt-rPT/strings.xml
+++ b/chips/sample/res/values-pt-rPT/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Amostra de Chips"</string>
     <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/chips/sample/res/values-pt/strings.xml b/chips/sample/res/values-pt/strings.xml
index 5f8e346..9d2e732 100644
--- a/chips/sample/res/values-pt/strings.xml
+++ b/chips/sample/res/values-pt/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Amostra de chips"</string>
     <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/chips/sample/res/values-ro/strings.xml b/chips/sample/res/values-ro/strings.xml
index eb71526..bcffb5e 100644
--- a/chips/sample/res/values-ro/strings.xml
+++ b/chips/sample/res/values-ro/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Mostră Chips"</string>
     <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/chips/sample/res/values-ru/strings.xml b/chips/sample/res/values-ru/strings.xml
index 215e40a..10f052e 100644
--- a/chips/sample/res/values-ru/strings.xml
+++ b/chips/sample/res/values-ru/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Адреса эл. почты"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Номера телефонов"</string>
 </resources>
diff --git a/chips/sample/res/values-sk/strings.xml b/chips/sample/res/values-sk/strings.xml
index 11d71c5..1297298 100644
--- a/chips/sample/res/values-sk/strings.xml
+++ b/chips/sample/res/values-sk/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Ukážka čipov"</string>
     <string name="email_addresses" msgid="5320415175940315400">"E-mailové adresy"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Telefónne čísla"</string>
 </resources>
diff --git a/chips/sample/res/values-sl/strings.xml b/chips/sample/res/values-sl/strings.xml
index 1e1c566..0e1c855 100644
--- a/chips/sample/res/values-sl/strings.xml
+++ b/chips/sample/res/values-sl/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Vzorec čipov"</string>
     <string name="email_addresses" msgid="5320415175940315400">"E-poštni naslovi"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Telefonske številke"</string>
 </resources>
diff --git a/chips/sample/res/values-sr/strings.xml b/chips/sample/res/values-sr/strings.xml
index 4bca249..dbd91a5 100644
--- a/chips/sample/res/values-sr/strings.xml
+++ b/chips/sample/res/values-sr/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Пример чипова"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Адресе е-поште"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Бројеви телефона"</string>
 </resources>
diff --git a/chips/sample/res/values-sv/strings.xml b/chips/sample/res/values-sv/strings.xml
index d7f6773..d787c85 100644
--- a/chips/sample/res/values-sv/strings.xml
+++ b/chips/sample/res/values-sv/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chipsprov"</string>
     <string name="email_addresses" msgid="5320415175940315400">"E-postadresser"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Telefonnummer"</string>
 </resources>
diff --git a/chips/sample/res/values-sw/strings.xml b/chips/sample/res/values-sw/strings.xml
index 62f4048..5afd792 100644
--- a/chips/sample/res/values-sw/strings.xml
+++ b/chips/sample/res/values-sw/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Sampuli ya Chips"</string>
     <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/chips/sample/res/values-th/strings.xml b/chips/sample/res/values-th/strings.xml
index 3658256..80bf67d 100644
--- a/chips/sample/res/values-th/strings.xml
+++ b/chips/sample/res/values-th/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"ตัวอย่างชิป"</string>
     <string name="email_addresses" msgid="5320415175940315400">"ที่อยู่อีเมล"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"หมายเลขโทรศัพท์"</string>
 </resources>
diff --git a/chips/sample/res/values-tl/strings.xml b/chips/sample/res/values-tl/strings.xml
index 96cd1c7..411e0d4 100644
--- a/chips/sample/res/values-tl/strings.xml
+++ b/chips/sample/res/values-tl/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Sample ng Mga Chip"</string>
     <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/chips/sample/res/values-tr/strings.xml b/chips/sample/res/values-tr/strings.xml
index 13395ab..dad01bb 100644
--- a/chips/sample/res/values-tr/strings.xml
+++ b/chips/sample/res/values-tr/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Fiş Örneği"</string>
     <string name="email_addresses" msgid="5320415175940315400">"E-posta Adresleri"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Telefon Numaraları"</string>
 </resources>
diff --git a/chips/sample/res/values-uk/strings.xml b/chips/sample/res/values-uk/strings.xml
index cdf5837..f09cb8c 100644
--- a/chips/sample/res/values-uk/strings.xml
+++ b/chips/sample/res/values-uk/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Електронні адреси"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Номери телефонів"</string>
 </resources>
diff --git a/chips/sample/res/values-vi/strings.xml b/chips/sample/res/values-vi/strings.xml
index a93a8c9..b9bc474 100644
--- a/chips/sample/res/values-vi/strings.xml
+++ b/chips/sample/res/values-vi/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Mẫu chip"</string>
     <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/chips/sample/res/values-zh-rCN/strings.xml b/chips/sample/res/values-zh-rCN/strings.xml
index 40f6ef1..ebee45c 100644
--- a/chips/sample/res/values-zh-rCN/strings.xml
+++ b/chips/sample/res/values-zh-rCN/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"电子邮件地址"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"电话号码"</string>
 </resources>
diff --git a/chips/sample/res/values-zh-rHK/strings.xml b/chips/sample/res/values-zh-rHK/strings.xml
index 18c7f2e..d2c3bb0 100644
--- a/chips/sample/res/values-zh-rHK/strings.xml
+++ b/chips/sample/res/values-zh-rHK/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"電郵地址"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"電話號碼"</string>
 </resources>
diff --git a/chips/sample/res/values-zh-rTW/strings.xml b/chips/sample/res/values-zh-rTW/strings.xml
index 971df5d..b502833 100644
--- a/chips/sample/res/values-zh-rTW/strings.xml
+++ b/chips/sample/res/values-zh-rTW/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Chips Sample"</string>
     <string name="email_addresses" msgid="5320415175940315400">"電子郵件地址"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"電話號碼"</string>
 </resources>
diff --git a/chips/sample/res/values-zu/strings.xml b/chips/sample/res/values-zu/strings.xml
index de6722b..6a106b7 100644
--- a/chips/sample/res/values-zu/strings.xml
+++ b/chips/sample/res/values-zu/strings.xml
@@ -16,6 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="4076638519189386225">"Isempula yama-chip"</string>
     <string name="email_addresses" msgid="5320415175940315400">"Amakheli we-imeyili"</string>
     <string name="phone_numbers" msgid="7836326833170390688">"Izinombolo zefoni"</string>
 </resources>
diff --git a/chips/src/com/android/ex/chips/BaseRecipientAdapter.java b/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
index b779776..468e168 100644
--- a/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
+++ b/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
@@ -43,12 +43,13 @@
 import android.widget.BaseAdapter;
 import android.widget.Filter;
 import android.widget.Filterable;
-import android.widget.ImageView;
-import android.widget.TextView;
+
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
 
 import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
@@ -59,8 +60,7 @@
 /**
  * Adapter for showing a recipient list.
  */
-public abstract class BaseRecipientAdapter extends BaseAdapter implements Filterable,
-        AccountSpecifier {
+public class BaseRecipientAdapter extends BaseAdapter implements Filterable, AccountSpecifier {
     private static final String TAG = "BaseRecipientAdapter";
 
     private static final boolean DEBUG = false;
@@ -149,10 +149,11 @@
         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 boolean isGalContact;
+        public final String lookupKey;
 
         public TemporaryEntry(
                 String displayName,
@@ -160,31 +161,34 @@
                 int destinationType,
                 String destinationLabel,
                 long contactId,
+                Long directoryId,
                 long dataId,
                 String thumbnailUriString,
                 int displayNameSource,
-                boolean isGalContact) {
+                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.isGalContact = isGalContact;
+            this.lookupKey = lookupKey;
         }
 
-        public TemporaryEntry(Cursor cursor, boolean isGalContact) {
+        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.isGalContact = isGalContact;
+            this.lookupKey = cursor.getString(Queries.Query.LOOKUP_KEY);
         }
     }
 
@@ -236,7 +240,8 @@
             }
 
             try {
-                defaultDirectoryCursor = doQuery(constraint, mPreferredMaxResultCount, null);
+                defaultDirectoryCursor = doQuery(constraint, mPreferredMaxResultCount,
+                        null /* directoryId */);
 
                 if (defaultDirectoryCursor == null) {
                     if (DEBUG) {
@@ -256,7 +261,7 @@
                         // Note: At this point each entry doesn't contain any photo
                         // (thus getPhotoBytes() returns null).
                         putOneEntry(new TemporaryEntry(defaultDirectoryCursor,
-                                false /* isGalContact */),
+                                null /* directoryId */),
                                 true, entryMap, nonAggregatedEntries, existingDestinations);
                     }
 
@@ -387,7 +392,7 @@
 
                     if (cursor != null) {
                         while (cursor.moveToNext()) {
-                            tempEntries.add(new TemporaryEntry(cursor, true /* isGalContact */));
+                            tempEntries.add(new TemporaryEntry(cursor, mParams.directoryId));
                         }
                     }
                 } finally {
@@ -460,6 +465,7 @@
     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
@@ -570,6 +576,15 @@
         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.
      */
@@ -687,8 +702,8 @@
                     entry.displayName,
                     entry.displayNameSource,
                     entry.destination, entry.destinationType, entry.destinationLabel,
-                    entry.contactId, entry.dataId, entry.thumbnailUriString, true,
-                    entry.isGalContact));
+                    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);
@@ -696,16 +711,16 @@
                     entry.displayName,
                     entry.displayNameSource,
                     entry.destination, entry.destinationType, entry.destinationLabel,
-                    entry.contactId, entry.dataId, entry.thumbnailUriString, true,
-                    entry.isGalContact));
+                    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.dataId, entry.thumbnailUriString, true,
-                    entry.isGalContact));
+                    entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
+                    true, entry.lookupKey));
             entryMap.put(entry.contactId, entryList);
         }
     }
@@ -749,7 +764,7 @@
     }
 
 
-    protected interface EntriesUpdatedObserver {
+    public interface EntriesUpdatedObserver {
         public void onChanged(List<RecipientEntry> entries);
     }
 
@@ -871,6 +886,39 @@
             } 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);
+                }
+            }
         }
     }
 
@@ -943,111 +991,12 @@
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
         final RecipientEntry entry = getEntries().get(position);
-        String displayName = entry.getDisplayName();
-        String destination = entry.getDestination();
-        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;
-            }
-        }
+        final String constraint = mCurrentConstraint == null ? null :
+                mCurrentConstraint.toString();
 
-        final View itemView = convertView != null ? convertView : mInflater.inflate(
-                getItemLayout(), parent, false);
-        final TextView displayNameView = (TextView) itemView.findViewById(getDisplayNameId());
-        final TextView destinationView = (TextView) itemView.findViewById(getDestinationId());
-        final TextView destinationTypeView = (TextView) itemView
-                .findViewById(getDestinationTypeId());
-        final ImageView imageView = (ImageView) itemView.findViewById(getPhotoId());
-        displayNameView.setText(displayName);
-        if (!TextUtils.isEmpty(destination)) {
-            destinationView.setText(destination);
-        } else {
-            destinationView.setText(null);
-        }
-        if (destinationTypeView != null) {
-            final CharSequence destinationType = mQuery
-                    .getTypeLabel(mContext.getResources(), entry.getDestinationType(),
-                            entry.getDestinationLabel()).toString().toUpperCase();
-
-            destinationTypeView.setText(destinationType);
-        }
-
-        if (entry.isFirstLevel()) {
-            displayNameView.setVisibility(View.VISIBLE);
-            if (imageView != null) {
-                imageView.setVisibility(View.VISIBLE);
-                final byte[] photoBytes = entry.getPhotoBytes();
-                if (photoBytes != null) {
-                    final Bitmap photo = BitmapFactory.decodeByteArray(photoBytes, 0,
-                            photoBytes.length);
-                    imageView.setImageBitmap(photo);
-                } else {
-                    imageView.setImageResource(getDefaultPhotoResource());
-                }
-            }
-        } else {
-            displayNameView.setVisibility(View.GONE);
-            if (imageView != null) {
-                imageView.setVisibility(View.INVISIBLE);
-            }
-        }
-        return itemView;
-    }
-
-    /**
-     * 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 #getDisplayNameId()},
-     * {@link #getDestinationId()}, and {@link #getPhotoId()}.
-     */
-    protected int getItemLayout() {
-        return R.layout.chips_recipient_dropdown_item;
-    }
-
-    /**
-     * Returns a resource ID representing an image which should be shown when ther's no relevant
-     * photo is available.
-     */
-    protected int getDefaultPhotoResource() {
-        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 getDisplayNameId() {
-        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 getDestinationId() {
-        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 getDestinationTypeId() {
-        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 getPhotoId() {
-        return android.R.id.icon;
+        return mDropdownChipLayouter.bindView(convertView, parent, entry, position,
+                AdapterType.BASE_RECIPIENT, constraint);
     }
 
     public Account getAccount() {
diff --git a/chips/src/com/android/ex/chips/DropdownChipLayouter.java b/chips/src/com/android/ex/chips/DropdownChipLayouter.java
new file mode 100644
index 0000000..6b0e78e
--- /dev/null
+++ b/chips/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/chips/src/com/android/ex/chips/Queries.java b/chips/src/com/android/ex/chips/Queries.java
index 9d31aec..1e66b96 100644
--- a/chips/src/com/android/ex/chips/Queries.java
+++ b/chips/src/com/android/ex/chips/Queries.java
@@ -18,6 +18,7 @@
 
 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;
@@ -28,14 +29,16 @@
 /* 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.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
@@ -46,14 +49,16 @@
     };
 
     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.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
@@ -76,8 +81,10 @@
         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) {
+        public Query(String[] projection, Uri contentFilter, Uri content) {
             mProjection = projection;
             mContentFilterUri = contentFilter;
             mContentUri = content;
diff --git a/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java b/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
index b9a7c80..f6f662d 100644
--- a/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
+++ b/chips/src/com/android/ex/chips/RecipientAlternatesAdapter.java
@@ -23,19 +23,18 @@
 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.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.CursorAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
 
 import com.android.ex.chips.BaseRecipientAdapter.DirectoryListQuery;
 import com.android.ex.chips.BaseRecipientAdapter.DirectorySearchParams;
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
 import com.android.ex.chips.Queries.Query;
 
 import java.util.ArrayList;
@@ -51,7 +50,6 @@
  */
 public class RecipientAlternatesAdapter extends CursorAdapter {
     static final int MAX_LOOKUPS = 50;
-    private final LayoutInflater mLayoutInflater;
 
     private final long mCurrentId;
 
@@ -63,7 +61,10 @@
 
     public static final int QUERY_TYPE_EMAIL = 0;
     public static final int QUERY_TYPE_PHONE = 1;
-    private Query mQuery;
+    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);
@@ -125,7 +126,7 @@
                     query.getProjection(),
                     query.getProjection()[Queries.Query.DESTINATION] + " IN ("
                             + bindString.toString() + ")", addressArray, null);
-            recipientEntries = processContactEntries(c);
+            recipientEntries = processContactEntries(c, null /* directoryId */);
             callback.matchesFound(recipientEntries);
         } finally {
             if (c != null) {
@@ -165,6 +166,7 @@
             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,
@@ -176,6 +178,7 @@
                                 directoryContactsCursor.close();
                                 directoryContactsCursor = null;
                             } else {
+                                directoryId = paramsList.get(i).directoryId;
                                 break;
                             }
                         }
@@ -183,7 +186,7 @@
                     if (directoryContactsCursor != null) {
                         try {
                             final Map<String, RecipientEntry> entries =
-                                    processContactEntries(directoryContactsCursor);
+                                    processContactEntries(directoryContactsCursor, directoryId);
 
                             for (final String address : entries.keySet()) {
                                 matchesNotFound.remove(address);
@@ -214,7 +217,8 @@
         callback.matchesNotFound(matchesNotFound);
     }
 
-    private static HashMap<String, RecipientEntry> processContactEntries(Cursor c) {
+    private static HashMap<String, RecipientEntry> processContactEntries(Cursor c,
+            Long directoryId) {
         HashMap<String, RecipientEntry> recipientEntries = new HashMap<String, RecipientEntry>();
         if (c != null && c.moveToFirst()) {
             do {
@@ -227,10 +231,11 @@
                         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,
-                        false /* isGalContact TODO(skennedy) We should look these up eventually */);
+                        c.getString(Queries.Query.LOOKUP_KEY));
 
                 /*
                  * In certain situations, we may have two results for one address, where one of the
@@ -327,46 +332,74 @@
         return cursor;
     }
 
-    public RecipientAlternatesAdapter(Context context, long contactId, long currentId,
-            OnCheckedItemChangedListener listener) {
-        this(context, contactId, currentId, QUERY_TYPE_EMAIL, listener);
-    }
-
-    public RecipientAlternatesAdapter(Context context, long contactId, long currentId,
-            int queryMode, OnCheckedItemChangedListener listener) {
-        super(context, getCursorForConstruction(context, contactId, queryMode), 0);
-        mLayoutInflater = LayoutInflater.from(context);
+    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;
 
-        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);
-        }
+        mDropdownChipLayouter = dropdownChipLayouter;
     }
 
-    private static Cursor getCursorForConstruction(Context context, long contactId, int queryType) {
+    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(
-                    Queries.EMAIL.getContentUri(),
+                    uri,
                     Queries.EMAIL.getProjection(),
-                    Queries.EMAIL.getProjection()[Queries.Query.CONTACT_ID] + " =?", new String[] {
+                    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(
-                    Queries.PHONE.getContentUri(),
+                    uri,
                     Queries.PHONE.getProjection(),
-                    Queries.PHONE.getProjection()[Queries.Query.CONTACT_ID] + " =?", new String[] {
+                    selection.toString(), new String[] {
                         String.valueOf(contactId)
                     }, null);
         }
-        return removeDuplicateDestinations(cursor);
+
+        final Cursor resultCursor = removeUndesiredDestinations(cursor, desiredMimeType, lookupKey);
+        cursor.close();
+
+        return resultCursor;
     }
 
     /**
@@ -379,22 +412,53 @@
      * - 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
-    /* package */ static Cursor removeDuplicateDestinations(Cursor original) {
+    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);
 
-            result.addRow(new Object[] {
+            final Object[] row = new Object[] {
                     original.getString(Query.NAME),
                     original.getString(Query.DESTINATION),
                     original.getInt(Query.DESTINATION_TYPE),
@@ -402,8 +466,48 @@
                     original.getLong(Query.CONTACT_ID),
                     original.getLong(Query.DATA_ID),
                     original.getString(Query.PHOTO_THUMBNAIL_URI),
-                    original.getInt(Query.DISPLAY_NAME_SOURCE)
-                    });
+                    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;
@@ -428,10 +532,11 @@
                 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,
-                false /* isGalContact TODO(skennedy) We should look these up eventually */);
+                c.getString(Queries.Query.LOOKUP_KEY));
     }
 
     @Override
@@ -439,7 +544,7 @@
         Cursor cursor = getCursor();
         cursor.moveToPosition(position);
         if (convertView == null) {
-            convertView = newView();
+            convertView = mDropdownChipLayouter.newView();
         }
         if (cursor.getLong(Queries.Query.DATA_ID) == mCurrentId) {
             mCheckedItemPosition = position;
@@ -451,44 +556,18 @@
         return convertView;
     }
 
-    // TODO: this is VERY similar to the BaseRecipientAdapter. Can we combine
-    // somehow?
     @Override
     public void bindView(View view, Context context, Cursor cursor) {
         int position = cursor.getPosition();
-
-        TextView display = (TextView) view.findViewById(android.R.id.title);
-        ImageView imageView = (ImageView) view.findViewById(android.R.id.icon);
         RecipientEntry entry = getRecipientEntry(position);
-        if (position == 0) {
-            display.setText(cursor.getString(Queries.Query.NAME));
-            display.setVisibility(View.VISIBLE);
-            // TODO: see if this needs to be done outside the main thread
-            // as it may be too slow to get immediately.
-            imageView.setImageURI(entry.getPhotoThumbnailUri());
-            imageView.setVisibility(View.VISIBLE);
-        } else {
-            display.setVisibility(View.GONE);
-            imageView.setVisibility(View.GONE);
-        }
-        TextView destination = (TextView) view.findViewById(android.R.id.text1);
-        destination.setText(cursor.getString(Queries.Query.DESTINATION));
 
-        TextView destinationType = (TextView) view.findViewById(android.R.id.text2);
-        if (destinationType != null) {
-            destinationType.setText(mQuery.getTypeLabel(context.getResources(),
-                    cursor.getInt(Queries.Query.DESTINATION_TYPE),
-                    cursor.getString(Queries.Query.DESTINATION_LABEL)).toString().toUpperCase());
-        }
+        mDropdownChipLayouter.bindView(view, null, entry, position,
+                AdapterType.RECIPIENT_ALTERNATES, null);
     }
 
     @Override
     public View newView(Context context, Cursor cursor, ViewGroup parent) {
-        return newView();
-    }
-
-    private View newView() {
-        return mLayoutInflater.inflate(R.layout.chips_recipient_dropdown_item, null);
+        return mDropdownChipLayouter.newView();
     }
 
     /*package*/ static interface OnCheckedItemChangedListener {
diff --git a/chips/src/com/android/ex/chips/RecipientEditTextView.java b/chips/src/com/android/ex/chips/RecipientEditTextView.java
index 3cba87b..4339b9e 100644
--- a/chips/src/com/android/ex/chips/RecipientEditTextView.java
+++ b/chips/src/com/android/ex/chips/RecipientEditTextView.java
@@ -30,6 +30,7 @@
 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;
@@ -149,14 +150,35 @@
 
     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 int mAlternatesLayout;
-
     private Bitmap mDefaultContactPhoto;
 
     private ImageSpan mMoreChip;
@@ -254,6 +276,8 @@
 
     private boolean mAttachedToWindow;
 
+    private DropdownChipLayouter mDropdownChipLayouter;
+
     public RecipientEditTextView(Context context, AttributeSet attrs) {
         super(context, attrs);
         setChipDimensions(context, attrs);
@@ -293,15 +317,23 @@
         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;
     }
 
@@ -433,20 +465,21 @@
     @Override
     public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
         super.setAdapter(adapter);
-        ((BaseRecipientAdapter) adapter)
-                .registerUpdateObserver(new BaseRecipientAdapter.EntriesUpdatedObserver() {
-                    @Override
-                    public void onChanged(List<RecipientEntry> entries) {
-                        // 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();
-                        }
-                    }
-                });
+        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);
     }
 
-    private void scrollBottomIntoView() {
+    protected void scrollBottomIntoView() {
         if (mScrollView != null && mShouldShrink) {
             int[] location = new int[2];
             getLocationOnScreen(location);
@@ -462,6 +495,10 @@
         }
     }
 
+    protected ScrollView getScrollView() {
+        return mScrollView;
+    }
+
     @Override
     public void performValidation() {
         // Do nothing. Chips handles its own validation.
@@ -543,121 +580,141 @@
                 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;
-        int deleteWidth = height;
+        // 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() - deleteWidth - widths[0]);
+                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(deleteWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
-                ellipsizedText.length()))
-                + (mChipPadding * 2) + deleteWidth);
+        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);
-        if (mChipBackgroundPressed != null) {
-            mChipBackgroundPressed.setBounds(0, 0, width, height);
-            mChipBackgroundPressed.draw(canvas);
-            paint.setColor(sSelectedTextColor);
-            // Vertically center the text in the chip.
-            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
-                    getTextYOffset((String) ellipsizedText, paint, height), paint);
-            // Make the delete a square.
-            Rect backgroundPadding = new Rect();
-            mChipBackgroundPressed.getPadding(backgroundPadding);
-            mChipDelete.setBounds(width - deleteWidth + backgroundPadding.left,
+
+        // 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,
-                    width - backgroundPadding.right,
+                    iconX + iconWidth,
                     height - backgroundPadding.bottom);
-            mChipDelete.draw(canvas);
-        } else {
-            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
+            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;
+    }
 
-    private Bitmap createUnselectedChip(RecipientEntry contact, TextPaint paint,
-            boolean leaveBlankIconSpacer) {
-        // Ellipsize the text so that it takes AT MOST the entire width of the
-        // autocomplete text entry area. Make sure to leave space for padding
-        // on the sides.
-        int height = (int) mChipHeight;
-        int iconWidth = height;
-        float[] widths = new float[1];
-        paint.getTextWidths(" ", widths);
-        CharSequence ellipsizedText = ellipsizeText(createChipDisplayText(contact), paint,
-                calculateAvailableWidth() - iconWidth - widths[0]);
-        // Make sure there is a minimum chip width so the user can ALWAYS
-        // tap a chip without difficulty.
-        int width = Math.max(iconWidth * 2, (int) Math.floor(paint.measureText(ellipsizedText, 0,
-                ellipsizedText.length()))
-                + (mChipPadding * 2) + iconWidth);
+    /**
+     * 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())));
 
-        // Create the background of the chip.
-        Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(tmpBitmap);
-        Drawable background = getChipBackground(contact);
-        if (background != null) {
-            background.setBounds(0, 0, width, height);
-            background.draw(canvas);
-
-            // 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();
-                }
-
-                Bitmap photo;
-                if (photoBytes != null) {
-                    photo = BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
-                } else {
-                    // TODO: can the scaled down default photo be cached?
-                    photo = mDefaultContactPhoto;
-                }
-                // Draw the photo on the left side.
-                if (photo != null) {
-                    RectF src = new RectF(0, 0, photo.getWidth(), photo.getHeight());
-                    Rect backgroundPadding = new Rect();
-                    mChipBackground.getPadding(backgroundPadding);
-                    RectF dst = new RectF(width - iconWidth + backgroundPadding.left,
-                            0 + backgroundPadding.top,
-                            width - backgroundPadding.right,
-                            height - backgroundPadding.bottom);
-                    Matrix matrix = new Matrix();
-                    matrix.setRectToRect(src, dst, Matrix.ScaleToFit.FILL);
-                    canvas.drawBitmap(photo, matrix, paint);
-                }
-            } else if (!leaveBlankIconSpacer || isPhoneQuery()) {
-                iconWidth = 0;
+        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();
             }
-            paint.setColor(getContext().getResources().getColor(android.R.color.black));
-            // Vertically center the text in the chip.
-            canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding,
-                    getTextYOffset((String)ellipsizedText, paint, height), paint);
-        } else {
-            Log.w(TAG, "Unable to draw a background for the chips as it was never set");
+            if (photoBytes != null) {
+                return BitmapFactory.decodeByteArray(photoBytes, 0, photoBytes.length);
+            } else {
+                // TODO: can the scaled down default photo be cached?
+                return mDefaultContactPhoto;
+            }
         }
-        return tmpBitmap;
+
+        return null;
     }
 
     /**
@@ -668,13 +725,26 @@
         return contact.isValid() ? mChipBackground : mInvalidChipBackground;
     }
 
-    private static float getTextYOffset(String text, TextPaint paint, int height) {
+    /**
+     * 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) {
@@ -697,13 +767,25 @@
         // 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);
+        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
@@ -749,11 +831,6 @@
         if (mChipPadding == -1) {
             mChipPadding = (int) r.getDimension(R.dimen.chip_padding);
         }
-        mAlternatesLayout = a.getResourceId(R.styleable.RecipientEditTextView_chipAlternatesLayout,
-                -1);
-        if (mAlternatesLayout == -1) {
-            mAlternatesLayout = R.layout.chips_alternate_item;
-        }
 
         mDefaultContactPhoto = BitmapFactory.decodeResource(r, R.drawable.ic_contact_picture);
 
@@ -772,6 +849,10 @@
         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();
@@ -779,6 +860,7 @@
             mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources()
                     .getDisplayMetrics());
         }
+
         a.recycle();
     }
 
@@ -798,6 +880,10 @@
         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
@@ -844,7 +930,8 @@
             Rect bounds;
             for (DrawableRecipientChip chip : chips) {
                 bounds = chip.getBounds();
-                if (getWidth() > 0 && bounds.right - bounds.left > getWidth()) {
+                if (getWidth() > 0 && bounds.right - bounds.left >
+                        getWidth() - getPaddingLeft() - getPaddingRight()) {
                     // Need to redraw that chip.
                     replaceChip(chip, chip.getEntry());
                 }
@@ -1376,9 +1463,11 @@
             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);
@@ -1510,13 +1599,14 @@
     }
 
     private ListAdapter createAlternatesAdapter(DrawableRecipientChip chip) {
-        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
-                getAdapter().getQueryType(), this);
+        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(), mAlternatesLayout, currentChip
-                .getEntry());
+        return new SingleRecipientArrayAdapter(getContext(), currentChip.getEntry(),
+                mDropdownChipLayouter);
     }
 
     @Override
@@ -1985,8 +2075,7 @@
             return constructChipSpan(
                     RecipientEntry.constructFakeEntry((String) text, isValid(text.toString())),
                     true, false);
-        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT
-                || currentChip.isGalContact()) {
+        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
             int start = getChipStart(currentChip);
             int end = getChipEnd(currentChip);
             getSpannable().removeSpan(currentChip);
@@ -2121,14 +2210,20 @@
         // Figure out the bounds of this chip and whether or not
         // the user clicked in the X portion.
         // TODO: Should x and y be used, or removed?
-        return chip.isSelected() && offset == getChipEnd(chip);
+        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.
-    /*pacakge*/ void removeChip(DrawableRecipientChip chip) {
+    /* package */void removeChip(DrawableRecipientChip chip) {
         Spannable spannable = getSpannable();
         int spanStart = spannable.getSpanStart(chip);
         int spanEnd = spannable.getSpanEnd(chip);
@@ -2232,6 +2327,7 @@
                 if (mMoreChip != null) {
                     spannable.removeSpan(mMoreChip);
                 }
+                clearSelectedChip();
                 return;
             }
             // Get whether there are any recipients pending addition to the
@@ -2522,7 +2618,7 @@
                     addresses.add(createAddressText(chip.getEntry()));
                 }
             }
-            final BaseRecipientAdapter adapter = (BaseRecipientAdapter) getAdapter();
+            final BaseRecipientAdapter adapter = getAdapter();
             RecipientAlternatesAdapter.getMatchingRecipients(getContext(), adapter, addresses,
                     adapter.getAccount(), new RecipientMatchCallback() {
                         @Override
@@ -2650,7 +2746,7 @@
                     addresses.add(createAddressText(chip.getEntry()));
                 }
             }
-            final BaseRecipientAdapter adapter = (BaseRecipientAdapter) getAdapter();
+            final BaseRecipientAdapter adapter = getAdapter();
             RecipientAlternatesAdapter.getMatchingRecipients(getContext(), adapter, addresses,
                     adapter.getAccount(),
                     new RecipientMatchCallback() {
diff --git a/chips/src/com/android/ex/chips/RecipientEntry.java b/chips/src/com/android/ex/chips/RecipientEntry.java
index 30fccae..7d9b87f 100644
--- a/chips/src/com/android/ex/chips/RecipientEntry.java
+++ b/chips/src/com/android/ex/chips/RecipientEntry.java
@@ -61,6 +61,8 @@
     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;
@@ -74,11 +76,13 @@
      */
     private byte[] mPhotoBytes;
 
-    private final boolean mIsGalContact;
+    /** 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 dataId,
-            Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid, boolean isGalContact) {
+            int destinationType, String destinationLabel, long contactId, Long directoryId,
+            long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid,
+            String lookupKey) {
         mEntryType = entryType;
         mIsFirstLevel = isFirstLevel;
         mDisplayName = displayName;
@@ -86,12 +90,13 @@
         mDestinationType = destinationType;
         mDestinationLabel = destinationLabel;
         mContactId = contactId;
+        mDirectoryId = directoryId;
         mDataId = dataId;
         mPhotoThumbnailUri = photoThumbnailUri;
         mPhotoBytes = null;
         mIsDivider = false;
         mIsValid = isValid;
-        mIsGalContact = isGalContact;
+        mLookupKey = lookupKey;
     }
 
     public boolean isValid() {
@@ -116,8 +121,8 @@
         final String tokenizedAddress = tokens.length > 0 ? tokens[0].getAddress() : address;
 
         return new RecipientEntry(ENTRY_TYPE_PERSON, tokenizedAddress, tokenizedAddress,
-                INVALID_DESTINATION_TYPE, null,
-                INVALID_CONTACT, INVALID_CONTACT, null, true, isValid, false /* isGalContact */);
+                INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
+                INVALID_CONTACT, null, true, isValid, null /* lookupKey */);
     }
 
     /**
@@ -126,8 +131,8 @@
     public static RecipientEntry constructFakePhoneEntry(final String phoneNumber,
             final boolean isValid) {
         return new RecipientEntry(ENTRY_TYPE_PERSON, phoneNumber, phoneNumber,
-                INVALID_DESTINATION_TYPE, null,
-                INVALID_CONTACT, INVALID_CONTACT, null, true, isValid, false /* isGalContact */);
+                INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
+                INVALID_CONTACT, null, true, isValid, null /* lookupKey */);
     }
 
     /**
@@ -149,35 +154,37 @@
     public static RecipientEntry constructGeneratedEntry(String display, String address,
             boolean isValid) {
         return new RecipientEntry(ENTRY_TYPE_PERSON, display, address, INVALID_DESTINATION_TYPE,
-                null, GENERATED_CONTACT, GENERATED_CONTACT, null, true, isValid,
-                false /* isGalContact */);
+                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 dataId, Uri photoThumbnailUri, boolean isValid, boolean isGalContact) {
+            Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid,
+            String lookupKey) {
         return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
                 displayName, destination), destination, destinationType, destinationLabel,
-                contactId, dataId, photoThumbnailUri, true, isValid, isGalContact);
+                contactId, directoryId, dataId, photoThumbnailUri, true, isValid, lookupKey);
     }
 
     public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
             String destination, int destinationType, String destinationLabel, long contactId,
-            long dataId, String thumbnailUriAsString, boolean isValid, boolean isGalContact) {
+            Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid,
+            String lookupKey) {
         return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
                 displayName, destination), destination, destinationType, destinationLabel,
-                contactId, dataId, (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString)
-                        : null), true, isValid, isGalContact);
+                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 dataId, String thumbnailUriAsString,
-            boolean isValid, boolean isGalContact) {
+            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, dataId, (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString)
-                        : null), false, isValid, isGalContact);
+                contactId, directoryId, dataId, (thumbnailUriAsString != null
+                        ? Uri.parse(thumbnailUriAsString) : null), false, isValid, lookupKey);
     }
 
     public int getEntryType() {
@@ -204,6 +211,10 @@
         return mContactId;
     }
 
+    public Long getDirectoryId() {
+        return mDirectoryId;
+    }
+
     public long getDataId() {
         return mDataId;
     }
@@ -234,8 +245,8 @@
         return mEntryType == ENTRY_TYPE_PERSON;
     }
 
-    public boolean isGalContact() {
-        return mIsGalContact;
+    public String getLookupKey() {
+        return mLookupKey;
     }
 
     @Override
diff --git a/chips/src/com/android/ex/chips/SingleRecipientArrayAdapter.java b/chips/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
index 0571a4e..985953f 100644
--- a/chips/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
+++ b/chips/src/com/android/ex/chips/SingleRecipientArrayAdapter.java
@@ -17,47 +17,27 @@
 package com.android.ex.chips;
 
 import android.content.Context;
-import android.text.util.Rfc822Tokenizer;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
+
+import com.android.ex.chips.DropdownChipLayouter.AdapterType;
 
 class SingleRecipientArrayAdapter extends ArrayAdapter<RecipientEntry> {
-    private int mLayoutId;
+    private final DropdownChipLayouter mDropdownChipLayouter;
 
-    private final LayoutInflater mLayoutInflater;
-
-    public SingleRecipientArrayAdapter(Context context, int resourceId, RecipientEntry entry) {
-        super(context, resourceId, new RecipientEntry[] {
+    public SingleRecipientArrayAdapter(Context context, RecipientEntry entry,
+            DropdownChipLayouter dropdownChipLayouter) {
+        super(context, dropdownChipLayouter.getAlternateItemLayoutResId(), new RecipientEntry[] {
             entry
         });
-        mLayoutInflater = LayoutInflater.from(context);
-        mLayoutId = resourceId;
+
+        mDropdownChipLayouter = dropdownChipLayouter;
     }
 
     @Override
     public View getView(int position, View convertView, ViewGroup parent) {
-        if (convertView == null) {
-            convertView = newView();
-        }
-        bindView(convertView, getItem(position));
-        return convertView;
-    }
-
-    private View newView() {
-        return mLayoutInflater.inflate(mLayoutId, null);
-    }
-
-    private static void bindView(View view, RecipientEntry entry) {
-        TextView display = (TextView) view.findViewById(android.R.id.title);
-        ImageView imageView = (ImageView) view.findViewById(android.R.id.icon);
-        display.setText(entry.getDisplayName());
-        display.setVisibility(View.VISIBLE);
-        imageView.setVisibility(View.VISIBLE);
-        TextView destination = (TextView) view.findViewById(android.R.id.text1);
-        destination.setText(Rfc822Tokenizer.tokenize(entry.getDestination())[0].getAddress());
+        return mDropdownChipLayouter.bindView(convertView, parent, getItem(position), position,
+                AdapterType.SINGLE_RECIPIENT, null);
     }
 }
diff --git a/chips/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java b/chips/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java
index 032d3b2..8012b5c 100644
--- a/chips/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java
+++ b/chips/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java
@@ -50,6 +50,16 @@
     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();
@@ -70,11 +80,4 @@
      * before any reverse lookups.
      */
     CharSequence getOriginalText();
-
-    /**
-     * Checks if this contact was retrieved from a GAL lookup.
-     *
-     * @return <code>true</code> if it came from GAL, <code>false</code> otherwise
-     */
-    boolean isGalContact();
 }
diff --git a/chips/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java b/chips/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java
index 11a66da..455f2cb 100644
--- a/chips/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java
+++ b/chips/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java
@@ -62,6 +62,16 @@
     }
 
     @Override
+    public Long getDirectoryId() {
+        return mDelegate.getDirectoryId();
+    }
+
+    @Override
+    public String getLookupKey() {
+        return mDelegate.getLookupKey();
+    }
+
+    @Override
     public long getDataId() {
         return mDelegate.getDataId();
     }
@@ -82,11 +92,6 @@
     }
 
     @Override
-    public boolean isGalContact() {
-        return mDelegate.isGalContact();
-    }
-
-    @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.
diff --git a/chips/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java b/chips/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java
index ac8e897..533f53f 100644
--- a/chips/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java
+++ b/chips/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java
@@ -27,6 +27,10 @@
 
     private final long mContactId;
 
+    private final Long mDirectoryId;
+
+    private final String mLookupKey;
+
     private final long mDataId;
 
     private final RecipientEntry mEntry;
@@ -39,6 +43,8 @@
         mDisplay = entry.getDisplayName();
         mValue = entry.getDestination().trim();
         mContactId = entry.getContactId();
+        mDirectoryId = entry.getDirectoryId();
+        mLookupKey = entry.getLookupKey();
         mDataId = entry.getDataId();
         mEntry = entry;
     }
@@ -69,6 +75,16 @@
     }
 
     @Override
+    public Long getDirectoryId() {
+        return mDirectoryId;
+    }
+
+    @Override
+    public String getLookupKey() {
+        return mLookupKey;
+    }
+
+    @Override
     public long getDataId() {
         return mDataId;
     }
@@ -93,11 +109,6 @@
     }
 
     @Override
-    public boolean isGalContact() {
-        return mEntry.isGalContact();
-    }
-
-    @Override
     public String toString() {
         return mDisplay + " <" + mValue + ">";
     }
diff --git a/chips/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java b/chips/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java
index 4637f69..6d3d27d 100644
--- a/chips/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java
+++ b/chips/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java
@@ -32,7 +32,12 @@
     private final SimpleRecipientChip mDelegate;
 
     public VisibleRecipientChip(final Drawable drawable, final RecipientEntry entry) {
-        super(drawable, DynamicDrawableSpan.ALIGN_BOTTOM);
+        this(drawable, entry, DynamicDrawableSpan.ALIGN_BOTTOM);
+    }
+
+    public VisibleRecipientChip(final Drawable drawable, final RecipientEntry entry,
+            final int verticalAlignment) {
+        super(drawable, verticalAlignment);
 
         mDelegate = new SimpleRecipientChip(entry);
     }
@@ -63,6 +68,16 @@
     }
 
     @Override
+    public Long getDirectoryId() {
+        return mDelegate.getDirectoryId();
+    }
+
+    @Override
+    public String getLookupKey() {
+        return mDelegate.getLookupKey();
+    }
+
+    @Override
     public long getDataId() {
         return mDelegate.getDataId();
     }
@@ -83,11 +98,6 @@
     }
 
     @Override
-    public boolean isGalContact() {
-        return mDelegate.isGalContact();
-    }
-
-    @Override
     public Rect getBounds() {
         return getDrawable().getBounds();
     }
diff --git a/chips/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java b/chips/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
index d4c0460..afb6a00 100644
--- a/chips/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
+++ b/chips/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
@@ -27,25 +27,28 @@
 
 public class RecipientAlternatesAdapterTest extends AndroidTestCase {
 
-    public void testRemoveDuplicateDestinations() {
+    public void testRemoveUndesiredDestinations() {
         MatrixCursor c = new MatrixCursor(Queries.EMAIL.getProjection());
         Cursor result;
 
         // Test: Empty input
-        assertEquals(0, RecipientAlternatesAdapter.removeDuplicateDestinations(c).getCount());
+        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.removeDuplicateDestinations(c);
+        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.removeDuplicateDestinations(c);
+        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);
@@ -54,7 +57,8 @@
         addRow(c, "ax", "1@android.com", 11, "homex", 10001, 2000, "xx", 1);
 
         // Third row should be removed.
-        result = RecipientAlternatesAdapter.removeDuplicateDestinations(c);
+        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);
@@ -63,7 +67,8 @@
         addRow(c, "ax", "2@android.com", 11, "homex", 10001, 2000, "xx", 1);
 
         // Forth row should also be removed.
-        result = RecipientAlternatesAdapter.removeDuplicateDestinations(c);
+        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);
@@ -120,8 +125,8 @@
         {
             final RecipientEntry entry1 =
                     RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
-                            "1@android.com", 0, null, 0, 0, (Uri) null, true,
-                            false /* isGalContact */);
+                            "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);
@@ -133,12 +138,12 @@
         {
             final RecipientEntry entry1 =
                     RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
-                            "1@android.com", 0, null, 0, 0, (Uri) null, true,
-                            false /* isGalContact */);
+                            "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, 0, (Uri) null, true,
-                            false /* isGalContact */);
+                            "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);
@@ -148,12 +153,12 @@
         {
             final RecipientEntry entry1 =
                     RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
-                            "1@android.com", 0, null, 0, 0, Uri.parse("http://www.android.com"),
-                            true, false /* isGalContact */);
+                            "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, 0, (Uri) null, true,
-                            false /* isGalContact */);
+                            "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);
diff --git a/common/java/com/android/common/NetworkConnectivityListener.java b/common/java/com/android/common/NetworkConnectivityListener.java
index b49b80d..187b1f1 100644
--- a/common/java/com/android/common/NetworkConnectivityListener.java
+++ b/common/java/com/android/common/NetworkConnectivityListener.java
@@ -36,6 +36,7 @@
  * (or poll ConnectivityManager) directly.
  * {@hide}
  */
+@Deprecated
 public class NetworkConnectivityListener {
     private static final String TAG = "NetworkConnectivityListener";
     private static final boolean DBG = false;
@@ -102,7 +103,7 @@
                 target.sendMessage(message);
             }
         }
-    };
+    }
 
     public enum State {
         UNKNOWN,
diff --git a/common/java/com/android/common/Rfc822Validator.java b/common/java/com/android/common/Rfc822Validator.java
index 2db00ff..bb77508 100644
--- a/common/java/com/android/common/Rfc822Validator.java
+++ b/common/java/com/android/common/Rfc822Validator.java
@@ -19,6 +19,7 @@
 import android.text.TextUtils;
 import android.text.util.Rfc822Token;
 import android.text.util.Rfc822Tokenizer;
+import android.util.Patterns;
 import android.widget.AutoCompleteTextView;
 
 import java.util.regex.Pattern;
@@ -38,15 +39,45 @@
  */
 @Deprecated
 public class Rfc822Validator implements AutoCompleteTextView.Validator {
-    /*
-     * Regex.EMAIL_ADDRESS_PATTERN hardcodes the TLD that we accept, but we
-     * want to make sure we will keep accepting email addresses with TLD's
-     * that don't exist at the time of this writing, so this regexp relaxes
-     * that constraint by accepting any kind of top level domain, not just
-     * ".com", ".fr", etc...
+    /**
+     * Expression that matches the local part of an email address.
+     * This expression does not follow the constraints of the RFC towards the dots, because the
+     * de facto standard is to allow them anywhere.
+     *
+     * It is however a simplification and it will not validate the double-quote syntax.
+     */
+    private static final String EMAIL_ADDRESS_LOCALPART_REGEXP =
+        "((?!\\s)[\\.\\w!#$%&'*+\\-/=?^`{|}~\u0080-\uFFFE])+";
+
+    /**
+     * Alias of characters that can be used in IRI, as per RFC 3987.
+     */
+    private static final String GOOD_IRI_CHAR = Patterns.GOOD_IRI_CHAR;
+
+    /**
+     * Regular expression for a domain label, as per RFC 3490.
+     * Its total length must not exceed 63 octets, according to RFC 5890.
+     */
+    private static final String LABEL_REGEXP =
+        "([" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,61})?[" + GOOD_IRI_CHAR + "]";
+
+    /**
+     * Expression that matches a domain name, including international domain names in Punycode or
+     * Unicode.
+     */
+    private static final String DOMAIN_REGEXP =
+        "("+ LABEL_REGEXP + "\\.)+"                 // Subdomains and domain
+        // Top-level domain must be at least 2 chars
+        + "[" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]";
+
+    /**
+     * Pattern for an email address.
+     *
+     * It is similar to {@link android.util.Patterns#EMAIL_ADDRESS}, but also accepts Unicode
+     * characters.
      */
     private static final Pattern EMAIL_ADDRESS_PATTERN =
-            Pattern.compile("[^\\s@]+@([^\\s@\\.]+\\.)+[a-zA-z][a-zA-Z][a-zA-Z]*");
+            Pattern.compile(EMAIL_ADDRESS_LOCALPART_REGEXP + "@" + DOMAIN_REGEXP);
 
     private String mDomain;
     private boolean mRemoveInvalid = false;
@@ -64,7 +95,6 @@
      */
     public boolean isValid(CharSequence text) {
         Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(text);
-
         return tokens.length == 1 &&
                EMAIL_ADDRESS_PATTERN.
                    matcher(tokens[0].getAddress()).matches();
diff --git a/common/tests/src/com/android/common/Rfc822ValidatorTest.java b/common/tests/src/com/android/common/Rfc822ValidatorTest.java
index cbcc812..61b8f25 100644
--- a/common/tests/src/com/android/common/Rfc822ValidatorTest.java
+++ b/common/tests/src/com/android/common/Rfc822ValidatorTest.java
@@ -18,30 +18,74 @@
 
 import android.test.suitebuilder.annotation.SmallTest;
 
+import junit.framework.TestCase;
+
 import java.util.HashMap;
 import java.util.Map;
 
-import junit.framework.TestCase;
-
 public class Rfc822ValidatorTest extends TestCase {
+    static final String[] VALID_EMAILS = new String[] {
+            "a@example.org", "b@exemple.fr", "c@d.e-f",
+            "Very.Common@example.org",
+            "john@EXAMPLE.ORG",
+            "john@a123b.c-d.dept.example.com",
+            "xn--r8jz45g@example.com",
+            "disposable.style.email.with+symbol@example.com",
+            "other.email-with-dash@example.com",
+            "!#$%&'*+-/=?^_`{}|~@example.com",  // Use of allowed special characters.
+            "a@domain-label-cannot-be-longer-than-63-chars-and-this-is-maximum.example.com",
+            // Valid de facto, even if RFC doesn't allow it.
+            "a..b@example.com", ".a@example.com", "b.@example.com",
+            // Punycode is an ASCII representation of International domain names.
+            "john.doe@xn--r8jz45g.xn--zckzah",
+            "john.doe@XN--R8JZ45G.XN--ZXKZAH",
+            "xn--r8jz45g@xn--r8jz45g.XN--ZXKZAH",
+            // Quoted address.
+            // TODO(regisd) Fix Rfc822Tokenizer which loses the quotes.
+            // "\"much.more unusual\"",
+            // "\"very.unusual.@.unusual.com\""
+
+            // Valid only in new Internalized email address.
+             "a@\u00E9.example.com",
+            //"みんな@例え.テスト",
+            "\u307F\u3093\u306A@\u4F8B\u3048.\u30C6\u30B9\u30C8",
+            // "test@test.テスト", // Unicode in TLD only.
+            "everybody@example.\u30C6\u30B9\u30C8",
+            // "test@例え.test", // Unicode in domain only.
+            "everybody@\u4F8B\u3048.test",
+            // "みんな@example.com" // Unicode in localpart only.
+            "\u307F\u3093\u306A@example.test"
+    };
+
+    static final String[] INVALID_EMAILS = new String[] {
+            "a", "example.com", "john.example.com", // Missing at sign.
+            "a b", "a space@example.com", // Space not allowed.
+            // Invalid domain.
+            "john@example..com", "a@b", "a@-b.com", "a@b-.com", "a@b.c",
+            "a@a123456789-123456789-123456789-123456789-123456789-123456789-bcd.example.com",
+            // Invalid characters in domain as per RFC 1034 and RFC 1035,
+            // even if these characters are in RFC5322's domain production.
+            "a@d_e.fg", "a@d!e.fg", "a@d#e.fg", "a@d$e.fg", "a@d%e.fg", "a@d&e.fg", "a@d'e.fg",
+            "a@d*e.fg", "a@d+e.fg", "a@d/e.fg", "a@d=e.fg", "a@d?e.fg", "a@d^e.fg", "a@d{}e.fg",
+            "a@d|e.fg", "a@d~e.fg",
+            // The domain is too long
+            "no@domain-label-cannot-be-longer-than-63-chars-but-this-is-64-chars.com",
+            "john@doe@example.com", // @ must be unique.
+            // Incorrect double quote.
+            // TODO(regisd): Fix Rfc822tokenizer which strips the quotes
+            // "just\"not\"right@example.com", "\"just.not\\\"@example.com",
+            "this\\ still\\\"not\\\\allowed@example.com"
+    };
 
     @SmallTest
     public void testEmailValidator() {
         Rfc822Validator validator = new Rfc822Validator("gmail.com");
-        String[] validEmails = new String[] {
-            "a@b.com", "a@b.fr", "a+b@c.com", "a@b.info", "john@example.com", "john@example.fr",
-            "john@corp.example.com",
-        };
 
-        for (String email : validEmails) {
+        for (String email : VALID_EMAILS) {
             assertTrue(email + " should be a valid email address", validator.isValid(email));
         }
 
-        String[] invalidEmails = new String[] {
-            "a", "a@b", "a b", "a@b.12", "john@example..com", "johnexample.com", "john.example.com"
-        };
-
-        for (String email : invalidEmails) {
+        for (String email : INVALID_EMAILS) {
             assertFalse(email + " should not be a valid email address", validator.isValid(email));
         }
 
diff --git a/framesequence/Android.mk b/framesequence/Android.mk
new file mode 100644
index 0000000..cc2c16c
--- /dev/null
+++ b/framesequence/Android.mk
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android-common-framesequence
+LOCAL_SDK_VERSION := 8
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/framesequence/AndroidManifest.xml b/framesequence/AndroidManifest.xml
new file mode 100644
index 0000000..f815643
--- /dev/null
+++ b/framesequence/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.support.rastermill">
+    <uses-sdk android:minSdkVersion="8"/>
+</manifest>
diff --git a/framesequence/build.xml b/framesequence/build.xml
new file mode 100644
index 0000000..b977ef7
--- /dev/null
+++ b/framesequence/build.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="rastermill" default="help">
+
+    <!-- The local.properties file is created and updated by the 'android' tool.
+         It contains the path to the SDK. It should *NOT* be checked into
+         Version Control Systems. -->
+    <property file="local.properties" />
+
+    <!-- The ant.properties file can be created by you. It is only edited by the
+         'android' tool to add properties to it.
+         This is the place to change some Ant specific build properties.
+         Here are some properties you may want to change/update:
+
+         source.dir
+             The name of the source directory. Default is 'src'.
+         out.dir
+             The name of the output directory. Default is 'bin'.
+
+         For other overridable properties, look at the beginning of the rules
+         files in the SDK, at tools/ant/build.xml
+
+         Properties related to the SDK location or the project target should
+         be updated using the 'android' tool with the 'update' action.
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems.
+
+         -->
+    <property file="ant.properties" />
+
+    <property name="export.dir" value="exported_libs" />
+
+    <!-- if sdk.dir was not set from one of the property file, then
+         get it from the ANDROID_HOME env var.
+         This must be done before we load project.properties since
+         the proguard config can use sdk.dir -->
+    <property environment="env" />
+    <condition property="sdk.dir" value="${env.ANDROID_HOME}">
+        <isset property="env.ANDROID_HOME" />
+    </condition>
+
+    <!-- The project.properties file is created and updated by the 'android'
+         tool, as well as ADT.
+
+         This contains project specific properties such as project target, and library
+         dependencies. Lower level build properties are stored in ant.properties
+         (or in .classpath for Eclipse projects).
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems. -->
+    <loadproperties srcFile="project.properties" />
+
+    <!-- quick check on sdk.dir -->
+    <fail
+            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
+            unless="sdk.dir"
+    />
+
+    <target name="-pre-build">
+        <exec executable="ndk-build" failonerror="true"/>
+    </target>
+
+    <target name="clean" depends="android_rules.clean">
+        <exec executable="ndk-build" failonerror="true">
+            <arg value="clean"/>
+        </exec>
+        <delete dir="${export.dir}" />
+    </target>
+
+    <!--
+        Import per project custom build rules if present at the root of the project.
+        This is the place to put custom intermediary targets such as:
+            -pre-build
+            -pre-compile
+            -post-compile (This is typically used for code obfuscation.
+                           Compiled code location: ${out.classes.absolute.dir}
+                           If this is not done in place, override ${out.dex.input.absolute.dir})
+            -post-package
+            -post-build
+            -pre-clean
+    -->
+    <import file="custom_rules.xml" optional="true" />
+
+    <!-- Import the actual build file.
+
+         To customize existing targets, there are two options:
+         - Customize only one target:
+             - copy/paste the target into this file, *before* the
+               <import> task.
+             - customize it to your needs.
+         - Customize the whole content of build.xml
+             - copy/paste the content of the rules files (minus the top node)
+               into this file, replacing the <import> task.
+             - customize to your needs.
+
+         ***********************
+         ****** IMPORTANT ******
+         ***********************
+         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
+         in order to avoid having your file be overridden by tools such as "android update project"
+    -->
+    <!-- version-tag: 1 -->
+    <import file="${sdk.dir}/tools/ant/build.xml" />
+
+    <target name="-post-build">
+        <delete dir="${export.dir}" />
+        <copy file="${out.library.jar.file}" tofile="${export.dir}/rastermill.jar" />
+        <copy todir="${export.dir}">
+            <fileset dir="${native.libs.absolute.dir}">
+                <include name="**/librastermill-native.so" />
+            </fileset>
+        </copy>
+    </target>
+
+</project>
diff --git a/framesequence/jni/Android.mk b/framesequence/jni/Android.mk
new file mode 100644
index 0000000..ee86fc1
--- /dev/null
+++ b/framesequence/jni/Android.mk
@@ -0,0 +1,44 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+## Main library
+
+LOCAL_STATIC_LIBRARIES += libgif
+
+LOCAL_LDFLAGS := -llog -ljnigraphics
+
+LOCAL_C_INCLUDES := \
+	external/giflib
+
+LOCAL_MODULE    := libframesequence
+LOCAL_SRC_FILES := \
+	BitmapDecoderJNI.cpp \
+	FrameSequence.cpp \
+	FrameSequenceJNI.cpp \
+	FrameSequence_gif.cpp \
+	JNIHelpers.cpp \
+	Registry.cpp \
+	Stream.cpp
+
+LOCAL_CFLAGS += -Wall -Wno-unused-parameter -Wno-unused-variable -Wno-overloaded-virtual
+LOCAL_CFLAGS += -fvisibility=hidden
+
+LOCAL_SDK_VERSION := 8
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/framesequence/jni/Application.mk b/framesequence/jni/Application.mk
new file mode 100644
index 0000000..eb51358
--- /dev/null
+++ b/framesequence/jni/Application.mk
@@ -0,0 +1,6 @@
+APP_PLATFORM := android-8
+APP_ABI := armeabi-v7a
+LOCAL_ARM_NEON=true
+ARCH_ARM_HAVE_NEON=true
+# TODO: Have libjpeg do this
+APP_CFLAGS := -D__ARM_HAVE_NEON=1
diff --git a/framesequence/jni/BitmapDecoderJNI.cpp b/framesequence/jni/BitmapDecoderJNI.cpp
new file mode 100644
index 0000000..5fe04b4
--- /dev/null
+++ b/framesequence/jni/BitmapDecoderJNI.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "FancyDecoding"
+
+#include <android/bitmap.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "FrameSequenceJNI.h"
+#include "JNIHelpers.h"
+#include "Stream.h"
+#include "utils/log.h"
+
+void throwException(JNIEnv* env, const char* error) {
+    jclass clazz = env->FindClass("java/lang/RuntimeException");
+    env->ThrowNew(clazz, error);
+}
+
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+    JNIEnv* env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        return -1;
+    }
+    if (FrameSequence_OnLoad(env)) {
+        ALOGE("Failed to load FrameSequence");
+        return -1;
+    }
+    if (JavaStream_OnLoad(env)) {
+        ALOGE("Failed to load JavaStream");
+        return -1;
+    }
+
+    return JNI_VERSION_1_6;
+}
diff --git a/framesequence/jni/Color.h b/framesequence/jni/Color.h
new file mode 100644
index 0000000..e49c64a
--- /dev/null
+++ b/framesequence/jni/Color.h
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#ifndef RASTERMILL_COLOR_H
+#define RASTERMILL_COLOR_H
+
+#include <sys/types.h>
+
+typedef uint32_t Color8888;
+
+static const Color8888 COLOR_8888_ALPHA_MASK = 0xff000000; // TODO: handle endianness
+static const Color8888 TRANSPARENT = 0x0;
+
+// TODO: handle endianness
+#define ARGB_TO_COLOR8888(a, r, g, b) \
+    ((a) << 24 | (b) << 16 | (g) << 8 | (r))
+
+#endif // RASTERMILL_COLOR_H
diff --git a/framesequence/jni/FrameSequence.cpp b/framesequence/jni/FrameSequence.cpp
new file mode 100644
index 0000000..efcfefa
--- /dev/null
+++ b/framesequence/jni/FrameSequence.cpp
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+#include "FrameSequence.h"
+
+#include "Registry.h"
+
+FrameSequence* FrameSequence::create(Stream* stream) {
+    const RegistryEntry* entry = Registry::Find(stream);
+
+    if (!entry) return NULL;
+
+    FrameSequence* frameSequence = entry->createFrameSequence(stream);
+    if (!frameSequence->getFrameCount() ||
+            !frameSequence->getWidth() || !frameSequence->getHeight()) {
+        // invalid contents, abort
+        delete frameSequence;
+        return NULL;
+    }
+
+    return frameSequence;
+}
diff --git a/framesequence/jni/FrameSequence.h b/framesequence/jni/FrameSequence.h
new file mode 100644
index 0000000..6667cdd
--- /dev/null
+++ b/framesequence/jni/FrameSequence.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#ifndef RASTERMILL_FRAME_SEQUENCE_H
+#define RASTERMILL_FRAME_SEQUENCE_H
+
+#include "Stream.h"
+#include "Color.h"
+
+class FrameSequenceState {
+public:
+    /**
+     * Produces a frame of animation in the output buffer, drawing (at minimum) the delta since
+     * previousFrameNr (the current contents of the buffer), or from scratch if previousFrameNr is
+     * negative
+     *
+     * Returns frame's delay time in milliseconds.
+     */
+    virtual long drawFrame(int frameNr,
+            Color8888* outputPtr, int outputPixelStride, int previousFrameNr) = 0;
+    virtual ~FrameSequenceState() {}
+};
+
+class FrameSequence {
+public:
+    /**
+     * Creates a FrameSequence using data from the data stream
+     *
+     * Type determined by header information in the stream
+     */
+    static FrameSequence* create(Stream* stream);
+
+    virtual ~FrameSequence() {}
+    virtual int getWidth() const = 0;
+    virtual int getHeight() const = 0;
+    virtual bool isOpaque() const = 0;
+    virtual int getFrameCount() const = 0;
+    virtual int getDefaultLoopCount() const = 0;
+
+    virtual FrameSequenceState* createState() const = 0;
+};
+
+#endif //RASTERMILL_FRAME_SEQUENCE_H
diff --git a/framesequence/jni/FrameSequenceJNI.cpp b/framesequence/jni/FrameSequenceJNI.cpp
new file mode 100644
index 0000000..08a73bc
--- /dev/null
+++ b/framesequence/jni/FrameSequenceJNI.cpp
@@ -0,0 +1,165 @@
+/*
+ * 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.
+ */
+
+#include <android/bitmap.h>
+#include "JNIHelpers.h"
+#include "utils/log.h"
+#include "FrameSequence.h"
+
+#include "FrameSequenceJNI.h"
+
+#define JNI_PACKAGE "android/support/rastermill"
+
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+} gFrameSequenceClassInfo;
+
+////////////////////////////////////////////////////////////////////////////////
+// Frame sequence
+////////////////////////////////////////////////////////////////////////////////
+
+static jobject createJavaFrameSequence(JNIEnv* env, FrameSequence* frameSequence) {
+    if (!frameSequence) {
+        return NULL;
+    }
+    return env->NewObject(gFrameSequenceClassInfo.clazz, gFrameSequenceClassInfo.ctor,
+            reinterpret_cast<jlong>(frameSequence),
+            frameSequence->getWidth(),
+            frameSequence->getHeight(),
+            frameSequence->isOpaque(),
+            frameSequence->getFrameCount(),
+            frameSequence->getDefaultLoopCount());
+}
+
+static jobject nativeDecodeByteArray(JNIEnv* env, jobject clazz,
+        jbyteArray byteArray, jint offset, jint length) {
+    jbyte* bytes = reinterpret_cast<jbyte*>(env->GetPrimitiveArrayCritical(byteArray, NULL));
+    if (bytes == NULL) {
+        jniThrowException(env, ILLEGAL_STATE_EXEPTION,
+                "couldn't read array bytes");
+        return NULL;
+    }
+    MemoryStream stream(bytes + offset, length);
+    FrameSequence* frameSequence = FrameSequence::create(&stream);
+    env->ReleasePrimitiveArrayCritical(byteArray, bytes, 0);
+    return createJavaFrameSequence(env, frameSequence);
+}
+
+static jobject nativeDecodeStream(JNIEnv* env, jobject clazz,
+        jobject istream, jbyteArray byteArray) {
+    JavaInputStream stream(env, istream, byteArray);
+    FrameSequence* frameSequence = FrameSequence::create(&stream);
+    return createJavaFrameSequence(env, frameSequence);
+}
+
+static void nativeDestroyFrameSequence(JNIEnv* env, jobject clazz,
+        jlong frameSequenceLong) {
+    FrameSequence* frameSequence = reinterpret_cast<FrameSequence*>(frameSequenceLong);
+    delete frameSequence;
+}
+
+static jlong nativeCreateState(JNIEnv* env, jobject clazz, jlong frameSequenceLong) {
+    FrameSequence* frameSequence = reinterpret_cast<FrameSequence*>(frameSequenceLong);
+    FrameSequenceState* state = frameSequence->createState();
+    return reinterpret_cast<jlong>(state);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Frame sequence state
+////////////////////////////////////////////////////////////////////////////////
+
+static void nativeDestroyState(
+        JNIEnv* env, jobject clazz, jlong frameSequenceStateLong) {
+    FrameSequenceState* frameSequenceState =
+            reinterpret_cast<FrameSequenceState*>(frameSequenceStateLong);
+    delete frameSequenceState;
+}
+
+static jlong JNICALL nativeGetFrame(
+        JNIEnv* env, jobject clazz, jlong frameSequenceStateLong, jint frameNr,
+        jobject bitmap, jint previousFrameNr) {
+    FrameSequenceState* frameSequenceState =
+            reinterpret_cast<FrameSequenceState*>(frameSequenceStateLong);
+    int ret;
+    AndroidBitmapInfo info;
+    void* pixels;
+    if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) {
+
+        jniThrowException(env, ILLEGAL_STATE_EXEPTION,
+                "Couldn't get info from Bitmap ");
+        return 0;
+    }
+
+    if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) {
+        jniThrowException(env, ILLEGAL_STATE_EXEPTION,
+                "Bitmap pixels couldn't be locked");
+        return 0;
+    }
+
+    int pixelStride = info.stride >> 2;
+    jlong delayMs = frameSequenceState->drawFrame(frameNr,
+            (Color8888*) pixels, pixelStride, previousFrameNr);
+
+    AndroidBitmap_unlockPixels(env, bitmap);
+    return delayMs;
+}
+
+static JNINativeMethod gMethods[] = {
+    {   "nativeDecodeByteArray",
+        "([BII)L" JNI_PACKAGE "/FrameSequence;",
+        (void*) nativeDecodeByteArray
+    },
+    {   "nativeDecodeStream",
+        "(Ljava/io/InputStream;[B)L" JNI_PACKAGE "/FrameSequence;",
+        (void*) nativeDecodeStream
+    },
+    {   "nativeDestroyFrameSequence",
+        "(J)V",
+        (void*) nativeDestroyFrameSequence
+    },
+    {   "nativeCreateState",
+        "(J)J",
+        (void*) nativeCreateState
+    },
+    {   "nativeGetFrame",
+        "(JILandroid/graphics/Bitmap;I)J",
+        (void*) nativeGetFrame
+    },
+    {   "nativeDestroyState",
+        "(J)V",
+        (void*) nativeDestroyState
+    },
+};
+
+jint FrameSequence_OnLoad(JNIEnv* env) {
+    // Get jclass with env->FindClass.
+    // Register methods with env->RegisterNatives.
+    gFrameSequenceClassInfo.clazz = env->FindClass(JNI_PACKAGE "/FrameSequence");
+    if (!gFrameSequenceClassInfo.clazz) {
+        ALOGW("Failed to find " JNI_PACKAGE "/FrameSequence");
+        return -1;
+    }
+    gFrameSequenceClassInfo.clazz = (jclass)env->NewGlobalRef(gFrameSequenceClassInfo.clazz);
+
+    gFrameSequenceClassInfo.ctor = env->GetMethodID(gFrameSequenceClassInfo.clazz, "<init>", "(JIIZII)V");
+    if (!gFrameSequenceClassInfo.ctor) {
+        ALOGW("Failed to find constructor for FrameSequence - was it stripped?");
+        return -1;
+    }
+
+    return env->RegisterNatives(gFrameSequenceClassInfo.clazz, gMethods, METHOD_COUNT(gMethods));
+}
diff --git a/framesequence/jni/FrameSequenceJNI.h b/framesequence/jni/FrameSequenceJNI.h
new file mode 100644
index 0000000..a52df8a
--- /dev/null
+++ b/framesequence/jni/FrameSequenceJNI.h
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+#ifndef RASTERMILL_FRAMESEQUENCE_JNI
+#define RASTERMILL_FRAMESEQUENCE_JNI
+
+#include <jni.h>
+
+jint FrameSequence_OnLoad(JNIEnv* env);
+
+#endif // RASTERMILL_FRAMESEQUENCE_JNI
diff --git a/framesequence/jni/FrameSequence_gif.cpp b/framesequence/jni/FrameSequence_gif.cpp
new file mode 100644
index 0000000..daa097b
--- /dev/null
+++ b/framesequence/jni/FrameSequence_gif.cpp
@@ -0,0 +1,366 @@
+/*
+ * 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.
+ */
+
+#include <string.h>
+#include "JNIHelpers.h"
+#include "utils/log.h"
+#include "utils/math.h"
+
+#include "FrameSequence_gif.h"
+
+#define GIF_DEBUG 0
+
+// These constants are chosen to imitate common browser behavior
+// Note that 0 delay is undefined behavior in the gif standard
+static const long MIN_DELAY_MS = 20;
+static const long DEFAULT_DELAY_MS = 100;
+
+static int streamReader(GifFileType* fileType, GifByteType* out, int size) {
+    Stream* stream = (Stream*) fileType->UserData;
+    return (int) stream->read(out, size);
+}
+
+static Color8888 gifColorToColor8888(const GifColorType& color) {
+    return ARGB_TO_COLOR8888(0xff, color.Red, color.Green, color.Blue);
+}
+
+static long getDelayMs(GraphicsControlBlock& gcb) {
+    long delayMs = gcb.DelayTime * 10;
+    if (delayMs < MIN_DELAY_MS) {
+        return DEFAULT_DELAY_MS;
+    }
+    return delayMs;
+}
+
+static bool willBeCleared(const GraphicsControlBlock& gcb) {
+    return gcb.DisposalMode == DISPOSE_BACKGROUND || gcb.DisposalMode == DISPOSE_PREVIOUS;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Frame sequence
+////////////////////////////////////////////////////////////////////////////////
+
+FrameSequence_gif::FrameSequence_gif(Stream* stream) :
+        mLoopCount(1), mBgColor(TRANSPARENT), mPreservedFrames(NULL), mRestoringFrames(NULL) {
+    mGif = DGifOpen(stream, streamReader, NULL);
+    if (!mGif) {
+        ALOGW("Gif load failed");
+        return;
+    }
+
+    if (DGifSlurp(mGif) != GIF_OK) {
+        ALOGW("Gif slurp failed");
+        DGifCloseFile(mGif);
+        mGif = NULL;
+        return;
+    }
+
+    long durationMs = 0;
+    int lastUnclearedFrame = -1;
+    mPreservedFrames = new bool[mGif->ImageCount];
+    mRestoringFrames = new int[mGif->ImageCount];
+
+    GraphicsControlBlock gcb;
+    for (int i = 0; i < mGif->ImageCount; i++) {
+        const SavedImage& image = mGif->SavedImages[i];
+
+        // find the loop extension pair
+        for (int j = 0; (j + 1) < image.ExtensionBlockCount; j++) {
+            ExtensionBlock* eb1 = image.ExtensionBlocks + j;
+            ExtensionBlock* eb2 = image.ExtensionBlocks + j + 1;
+            if (eb1->Function == APPLICATION_EXT_FUNC_CODE
+                    // look for "NETSCAPE2.0" app extension
+                    && eb1->ByteCount == 11
+                    && !memcmp((const char*)(eb1->Bytes), "NETSCAPE2.0", 11)
+                    // verify extension contents and get loop count
+                    && eb2->Function == CONTINUE_EXT_FUNC_CODE
+                    && eb2->ByteCount == 3
+                    && eb2->Bytes[0] == 1) {
+                mLoopCount = (int)(eb2->Bytes[2] & 0xff) + (int)(eb2->Bytes[1] & 0xff);
+            }
+        }
+
+        DGifSavedExtensionToGCB(mGif, i, &gcb);
+
+        // timing
+        durationMs += getDelayMs(gcb);
+
+        // preserve logic
+        mPreservedFrames[i] = false;
+        mRestoringFrames[i] = -1;
+        if (gcb.DisposalMode == DISPOSE_PREVIOUS && lastUnclearedFrame >= 0) {
+            mPreservedFrames[lastUnclearedFrame] = true;
+            mRestoringFrames[i] = lastUnclearedFrame;
+        }
+        if (!willBeCleared(gcb)) {
+            lastUnclearedFrame = i;
+        }
+    }
+
+#if GIF_DEBUG
+    ALOGD("FrameSequence_gif created with size %d %d, frames %d dur %ld",
+            mGif->SWidth, mGif->SHeight, mGif->ImageCount, durationMs);
+    for (int i = 0; i < mGif->ImageCount; i++) {
+        DGifSavedExtensionToGCB(mGif, i, &gcb);
+        ALOGD("    Frame %d - must preserve %d, restore point %d, trans color %d",
+                i, mPreservedFrames[i], mRestoringFrames[i], gcb.TransparentColor);
+    }
+#endif
+
+    if (mGif->SColorMap) {
+        // calculate bg color
+        GraphicsControlBlock gcb;
+        DGifSavedExtensionToGCB(mGif, 0, &gcb);
+        if (gcb.TransparentColor == NO_TRANSPARENT_COLOR) {
+            mBgColor = gifColorToColor8888(mGif->SColorMap->Colors[mGif->SBackGroundColor]);
+        }
+    }
+}
+
+FrameSequence_gif::~FrameSequence_gif() {
+    if (mGif) {
+        DGifCloseFile(mGif);
+    }
+    delete[] mPreservedFrames;
+    delete[] mRestoringFrames;
+}
+
+FrameSequenceState* FrameSequence_gif::createState() const {
+    return new FrameSequenceState_gif(*this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// draw helpers
+////////////////////////////////////////////////////////////////////////////////
+
+// return true if area of 'target' is completely covers area of 'covered'
+static bool checkIfCover(const GifImageDesc& target, const GifImageDesc& covered) {
+    return target.Left <= covered.Left
+            && covered.Left + covered.Width <= target.Left + target.Width
+            && target.Top <= covered.Top
+            && covered.Top + covered.Height <= target.Top + target.Height;
+}
+
+static void copyLine(Color8888* dst, const unsigned char* src, const ColorMapObject* cmap,
+                     int transparent, int width) {
+    for (; width > 0; width--, src++, dst++) {
+        if (*src != transparent) {
+            *dst = gifColorToColor8888(cmap->Colors[*src]);
+        }
+    }
+}
+
+static void setLineColor(Color8888* dst, Color8888 color, int width) {
+    for (; width > 0; width--, dst++) {
+        *dst = color;
+    }
+}
+
+static void getCopySize(const GifImageDesc& imageDesc, int maxWidth, int maxHeight,
+        GifWord& copyWidth, GifWord& copyHeight) {
+    copyWidth = imageDesc.Width;
+    if (imageDesc.Left + copyWidth > maxWidth) {
+        copyWidth = maxWidth - imageDesc.Left;
+    }
+    copyHeight = imageDesc.Height;
+    if (imageDesc.Top + copyHeight > maxHeight) {
+        copyHeight = maxHeight - imageDesc.Top;
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Frame sequence state
+////////////////////////////////////////////////////////////////////////////////
+
+FrameSequenceState_gif::FrameSequenceState_gif(const FrameSequence_gif& frameSequence) :
+    mFrameSequence(frameSequence), mPreserveBuffer(NULL), mPreserveBufferFrame(-1) {
+}
+
+FrameSequenceState_gif::~FrameSequenceState_gif() {
+       delete[] mPreserveBuffer;
+}
+
+void FrameSequenceState_gif::savePreserveBuffer(Color8888* outputPtr, int outputPixelStride, int frameNr) {
+    if (frameNr == mPreserveBufferFrame) return;
+
+    mPreserveBufferFrame = frameNr;
+    const int width = mFrameSequence.getWidth();
+    const int height = mFrameSequence.getHeight();
+    if (!mPreserveBuffer) {
+        mPreserveBuffer = new Color8888[width * height];
+    }
+    for (int y = 0; y < height; y++) {
+        memcpy(mPreserveBuffer + width * y,
+                outputPtr + outputPixelStride * y,
+                width * 4);
+    }
+}
+
+void FrameSequenceState_gif::restorePreserveBuffer(Color8888* outputPtr, int outputPixelStride) {
+    const int width = mFrameSequence.getWidth();
+    const int height = mFrameSequence.getHeight();
+    if (!mPreserveBuffer) {
+        ALOGD("preserve buffer not allocated! ah!");
+        return;
+    }
+    for (int y = 0; y < height; y++) {
+        memcpy(outputPtr + outputPixelStride * y,
+                mPreserveBuffer + width * y,
+                width * 4);
+    }
+}
+
+long FrameSequenceState_gif::drawFrame(int frameNr,
+        Color8888* outputPtr, int outputPixelStride, int previousFrameNr) {
+
+    GifFileType* gif = mFrameSequence.getGif();
+    if (!gif) {
+        ALOGD("Cannot drawFrame, mGif is NULL");
+        return -1;
+    }
+
+#if GIF_DEBUG
+    ALOGD("      drawFrame on %p nr %d on addr %p, previous frame nr %d",
+            this, frameNr, outputPtr, previousFrameNr);
+#endif
+
+    const int height = mFrameSequence.getHeight();
+    const int width = mFrameSequence.getWidth();
+
+    GraphicsControlBlock gcb;
+
+    int start = max(previousFrameNr + 1, 0);
+
+    for (int i = max(start - 1, 0); i < frameNr; i++) {
+        int neededPreservedFrame = mFrameSequence.getRestoringFrame(i);
+        if (neededPreservedFrame >= 0 && (mPreserveBufferFrame != neededPreservedFrame)) {
+#if GIF_DEBUG
+            ALOGD("frame %d needs frame %d preserved, but %d is currently, so drawing from scratch",
+                    i, neededPreservedFrame, mPreserveBufferFrame);
+#endif
+            start = 0;
+        }
+    }
+
+    for (int i = start; i <= frameNr; i++) {
+        DGifSavedExtensionToGCB(gif, i, &gcb);
+        const SavedImage& frame = gif->SavedImages[i];
+
+#if GIF_DEBUG
+        bool frameOpaque = gcb.TransparentColor == NO_TRANSPARENT_COLOR;
+        ALOGD("producing frame %d, drawing frame %d (opaque %d, disp %d, del %d)",
+                frameNr, i, frameOpaque, gcb.DisposalMode, gcb.DelayTime);
+#endif
+        if (i == 0) {
+            //clear bitmap
+            Color8888 bgColor = mFrameSequence.getBackgroundColor();
+            for (int y = 0; y < height; y++) {
+                for (int x = 0; x < width; x++) {
+                    outputPtr[y * outputPixelStride + x] = bgColor;
+                }
+            }
+        } else {
+            GraphicsControlBlock prevGcb;
+            DGifSavedExtensionToGCB(gif, i - 1, &prevGcb);
+            const SavedImage& prevFrame = gif->SavedImages[i - 1];
+            bool prevFrameDisposed = willBeCleared(prevGcb);
+
+            bool newFrameOpaque = gcb.TransparentColor == NO_TRANSPARENT_COLOR;
+            bool prevFrameCompletelyCovered = newFrameOpaque
+                    && checkIfCover(frame.ImageDesc, prevFrame.ImageDesc);
+
+            if (prevFrameDisposed && !prevFrameCompletelyCovered) {
+                switch (prevGcb.DisposalMode) {
+                case DISPOSE_BACKGROUND: {
+                    Color8888* dst = outputPtr + prevFrame.ImageDesc.Left +
+                            prevFrame.ImageDesc.Top * outputPixelStride;
+
+                    GifWord copyWidth, copyHeight;
+                    getCopySize(prevFrame.ImageDesc, width, height, copyWidth, copyHeight);
+                    for (; copyHeight > 0; copyHeight--) {
+                        setLineColor(dst, TRANSPARENT, copyWidth);
+                        dst += outputPixelStride;
+                    }
+                } break;
+                case DISPOSE_PREVIOUS: {
+                    restorePreserveBuffer(outputPtr, outputPixelStride);
+                } break;
+                }
+            }
+
+            if (mFrameSequence.getPreservedFrame(i - 1)) {
+                // currently drawn frame will be restored by a following DISPOSE_PREVIOUS draw, so
+                // we preserve it
+                savePreserveBuffer(outputPtr, outputPixelStride, i - 1);
+            }
+        }
+
+        bool willBeCleared = gcb.DisposalMode == DISPOSE_BACKGROUND
+                || gcb.DisposalMode == DISPOSE_PREVIOUS;
+        if (i == frameNr || !willBeCleared) {
+            const ColorMapObject* cmap = gif->SColorMap;
+            if (frame.ImageDesc.ColorMap) {
+                cmap = frame.ImageDesc.ColorMap;
+            }
+
+            if (cmap == NULL || cmap->ColorCount != (1 << cmap->BitsPerPixel)) {
+                ALOGW("Warning: potentially corrupt color map");
+            }
+
+            const unsigned char* src = (unsigned char*)frame.RasterBits;
+            Color8888* dst = outputPtr + frame.ImageDesc.Left +
+                    frame.ImageDesc.Top * outputPixelStride;
+            GifWord copyWidth, copyHeight;
+            getCopySize(frame.ImageDesc, width, height, copyWidth, copyHeight);
+            for (; copyHeight > 0; copyHeight--) {
+                copyLine(dst, src, cmap, gcb.TransparentColor, copyWidth);
+                src += frame.ImageDesc.Width;
+                dst += outputPixelStride;
+            }
+        }
+    }
+
+    // return last frame's delay
+    const int maxFrame = gif->ImageCount;
+    const int lastFrame = (frameNr + maxFrame - 1) % maxFrame;
+    DGifSavedExtensionToGCB(gif, lastFrame, &gcb);
+    return getDelayMs(gcb);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Registry
+////////////////////////////////////////////////////////////////////////////////
+
+#include "Registry.h"
+
+static bool isGif(void* header, int header_size) {
+    return !memcmp(GIF_STAMP, header, GIF_STAMP_LEN)
+            || !memcmp(GIF87_STAMP, header, GIF_STAMP_LEN)
+            || !memcmp(GIF89_STAMP, header, GIF_STAMP_LEN);
+}
+
+static FrameSequence* createFramesequence(Stream* stream) {
+    return new FrameSequence_gif(stream);
+}
+
+static RegistryEntry gEntry = {
+        GIF_STAMP_LEN,
+        isGif,
+        createFramesequence,
+        NULL,
+};
+static Registry gRegister(gEntry);
+
diff --git a/framesequence/jni/FrameSequence_gif.h b/framesequence/jni/FrameSequence_gif.h
new file mode 100644
index 0000000..8bf57b6
--- /dev/null
+++ b/framesequence/jni/FrameSequence_gif.h
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+#ifndef RASTERMILL_FRAMESQUENCE_GIF_H
+#define RASTERMILL_FRAMESQUENCE_GIF_H
+
+#include "config.h"
+#include "gif_lib.h"
+
+#include "Stream.h"
+#include "Color.h"
+#include "FrameSequence.h"
+
+class FrameSequence_gif : public FrameSequence {
+public:
+    FrameSequence_gif(Stream* stream);
+    virtual ~FrameSequence_gif();
+
+    virtual int getWidth() const {
+        return mGif ? mGif->SWidth : 0;
+    }
+
+    virtual int getHeight() const {
+        return mGif ? mGif->SHeight : 0;
+    }
+
+    virtual bool isOpaque() const {
+        return (mBgColor & COLOR_8888_ALPHA_MASK) == COLOR_8888_ALPHA_MASK;
+    }
+
+    virtual int getFrameCount() const {
+        return mGif ? mGif->ImageCount : 0;
+    }
+
+    virtual int getDefaultLoopCount() const {
+        return mLoopCount;
+    }
+
+    virtual FrameSequenceState* createState() const;
+
+    GifFileType* getGif() const { return mGif; }
+    Color8888 getBackgroundColor() const { return mBgColor; }
+    bool getPreservedFrame(int frameIndex) const { return mPreservedFrames[frameIndex]; }
+    int getRestoringFrame(int frameIndex) const { return mRestoringFrames[frameIndex]; }
+
+private:
+    GifFileType* mGif;
+    int mLoopCount;
+    Color8888 mBgColor;
+
+    // array of bool per frame - if true, frame data is used by a later DISPOSE_PREVIOUS frame
+    bool* mPreservedFrames;
+
+    // array of ints per frame - if >= 0, points to the index of the preserve that frame needs
+    int* mRestoringFrames;
+};
+
+class FrameSequenceState_gif : public FrameSequenceState {
+public:
+    FrameSequenceState_gif(const FrameSequence_gif& frameSequence);
+    virtual ~FrameSequenceState_gif();
+
+    // returns frame's delay time in ms
+    virtual long drawFrame(int frameNr,
+            Color8888* outputPtr, int outputPixelStride, int previousFrameNr);
+
+private:
+    void savePreserveBuffer(Color8888* outputPtr, int outputPixelStride, int frameNr);
+    void restorePreserveBuffer(Color8888* outputPtr, int outputPixelStride);
+
+    const FrameSequence_gif& mFrameSequence;
+    Color8888* mPreserveBuffer;
+    int mPreserveBufferFrame;
+};
+
+#endif //RASTERMILL_FRAMESQUENCE_GIF_H
diff --git a/framesequence/jni/JNIHelpers.cpp b/framesequence/jni/JNIHelpers.cpp
new file mode 100644
index 0000000..dd0c818
--- /dev/null
+++ b/framesequence/jni/JNIHelpers.cpp
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#include "JNIHelpers.h"
+#include "utils/log.h"
+
+void jniThrowException(JNIEnv* env, const char* className, const char* msg) {
+    jclass clazz = env->FindClass(className);
+    if (!clazz) {
+        ALOGE("Unable to find exception class %s", className);
+        /* ClassNotFoundException now pending */
+        return;
+    }
+
+    if (env->ThrowNew(clazz, msg) != JNI_OK) {
+        ALOGE("Failed throwing '%s' '%s'", className, msg);
+        /* an exception, most likely OOM, will now be pending */
+    }
+    env->DeleteLocalRef(clazz);
+}
diff --git a/framesequence/jni/JNIHelpers.h b/framesequence/jni/JNIHelpers.h
new file mode 100644
index 0000000..bb850d2
--- /dev/null
+++ b/framesequence/jni/JNIHelpers.h
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#ifndef RASTERMILL_JNIHELPERS_H
+#define RASTERMILL_JNIHELPERS_H
+
+#include <jni.h>
+
+#define METHOD_COUNT(methodArray) (sizeof(methodArray) / sizeof(methodArray[0]))
+
+#define ILLEGAL_STATE_EXEPTION "java/lang/IllegalStateException"
+
+void jniThrowException(JNIEnv* env, const char* className, const char* msg);
+
+
+#endif //RASTERMILL_JNIHELPERS_H
diff --git a/framesequence/jni/Registry.cpp b/framesequence/jni/Registry.cpp
new file mode 100644
index 0000000..125ac66
--- /dev/null
+++ b/framesequence/jni/Registry.cpp
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#include "Registry.h"
+
+#include "Stream.h"
+
+static Registry* gHead = 0;
+static int gHeaderBytesRequired = 0;
+
+Registry::Registry(const RegistryEntry& entry) {
+    mImpl = entry;
+
+    mNext = gHead;
+    gHead = this;
+
+    if (gHeaderBytesRequired < entry.requiredHeaderBytes) {
+        gHeaderBytesRequired = entry.requiredHeaderBytes;
+    }
+}
+
+const RegistryEntry* Registry::Find(Stream* stream) {
+    Registry* registry = gHead;
+    int headerSize = gHeaderBytesRequired;
+    char header[headerSize];
+    headerSize = stream->peek(header, headerSize);
+    while (registry) {
+        if (headerSize >= registry->mImpl.requiredHeaderBytes
+                && registry->mImpl.checkHeader(header, headerSize)) {
+            return &(registry->mImpl);
+        }
+        registry = registry->mNext;
+    }
+    return 0;
+}
diff --git a/framesequence/jni/Registry.h b/framesequence/jni/Registry.h
new file mode 100644
index 0000000..571c611
--- /dev/null
+++ b/framesequence/jni/Registry.h
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+#ifndef RASTERMILL_REGISTRY_H
+#define RASTERMILL_REGISTRY_H
+
+class FrameSequence;
+class Decoder;
+class Stream;
+
+struct RegistryEntry {
+    int requiredHeaderBytes;
+    bool (*checkHeader)(void* header, int header_size);
+    FrameSequence* (*createFrameSequence)(Stream* stream);
+    Decoder* (*createDecoder)(Stream* stream);
+};
+
+/**
+ * Template class for registering subclasses that can produce instances of themselves given a
+ * DataStream pointer.
+ *
+ * The super class / root constructable type only needs to define a single static construction
+ * meathod that creates an instance by iterating through all factory methods.
+ */
+class Registry {
+public:
+    Registry(const RegistryEntry& entry);
+
+    static const RegistryEntry* Find(Stream* stream);
+
+private:
+    RegistryEntry mImpl;
+    Registry* mNext;
+};
+
+#endif // RASTERMILL_REGISTRY_H
diff --git a/framesequence/jni/Stream.cpp b/framesequence/jni/Stream.cpp
new file mode 100644
index 0000000..b2e0c39
--- /dev/null
+++ b/framesequence/jni/Stream.cpp
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "Stream"
+
+#include "Stream.h"
+
+#include <string.h>
+
+#include "JNIHelpers.h"
+#include "utils/log.h"
+#include "utils/math.h"
+
+static struct {
+    jmethodID read;
+    jmethodID reset;
+} gInputStreamClassInfo;
+
+Stream::Stream()
+    : mPeekBuffer(0)
+    , mPeekSize(0)
+    , mPeekOffset(0) {
+}
+
+Stream::~Stream() {
+    delete mPeekBuffer;
+}
+
+size_t Stream::peek(void* buffer, size_t size) {
+    size_t peek_remaining = mPeekSize - mPeekOffset;
+    if (size > peek_remaining) {
+        char* old_peek = mPeekBuffer;
+        mPeekBuffer = new char[size];
+        if (old_peek) {
+            memcpy(mPeekBuffer, old_peek + mPeekOffset, peek_remaining);
+            delete old_peek;
+        }
+        size_t read = doRead(mPeekBuffer + mPeekOffset, size - peek_remaining);
+        mPeekOffset = 0;
+        mPeekSize = peek_remaining + read;
+    }
+    size = min(size, mPeekSize - mPeekOffset);
+    memcpy(buffer, mPeekBuffer + mPeekOffset, size);
+    return size;
+}
+
+size_t Stream::read(void* buffer, size_t size) {
+    size_t bytes_read = 0;
+    size_t peek_remaining = mPeekSize - mPeekOffset;
+    if (peek_remaining) {
+        bytes_read = min(size, peek_remaining);
+        memcpy(buffer, mPeekBuffer + mPeekOffset, bytes_read);
+        mPeekOffset += bytes_read;
+        if (mPeekOffset == mPeekSize) {
+            delete mPeekBuffer;
+            mPeekBuffer = 0;
+            mPeekOffset = 0;
+            mPeekSize = 0;
+        }
+        size -= bytes_read;
+        buffer = ((char*) buffer) + bytes_read;
+    }
+    if (size) {
+        bytes_read += doRead(buffer, size);
+    }
+    return bytes_read;
+}
+
+size_t MemoryStream::doRead(void* buffer, size_t size) {
+    size = min(size, mRemaining);
+    memcpy(buffer, mBuffer, size);
+    mBuffer += size;
+    mRemaining -= size;
+    return size;
+}
+
+size_t FileStream::doRead(void* buffer, size_t size) {
+    return fread(buffer, 1, size, mFd);
+}
+
+size_t JavaInputStream::doRead(void* dstBuffer, size_t size) {
+    size_t totalBytesRead = 0;
+
+    do {
+        size_t requested = min(size, mByteArrayLength);
+
+        jint bytesRead = mEnv->CallIntMethod(mInputStream,
+                gInputStreamClassInfo.read, mByteArray, 0, requested);
+        if (mEnv->ExceptionCheck() || bytesRead < 0) {
+            return 0;
+        }
+
+        mEnv->GetByteArrayRegion(mByteArray, 0, bytesRead, (jbyte*)dstBuffer);
+        dstBuffer = (char*)dstBuffer + bytesRead;
+        totalBytesRead += bytesRead;
+        size -= bytesRead;
+    } while (size > 0);
+
+    return totalBytesRead;
+}
+
+jint JavaStream_OnLoad(JNIEnv* env) {
+    // Skip the verbose logging on error for these, as they won't be subject
+    // to obfuscators or similar and are thus unlikely to ever fail
+    jclass inputStreamClazz = env->FindClass("java/io/InputStream");
+    if (!inputStreamClazz) {
+        return -1;
+    }
+    gInputStreamClassInfo.read = env->GetMethodID(inputStreamClazz, "read", "([BII)I");
+    gInputStreamClassInfo.reset = env->GetMethodID(inputStreamClazz, "reset", "()V");
+    if (!gInputStreamClassInfo.read || !gInputStreamClassInfo.reset) {
+        return -1;
+    }
+    return 0;
+}
diff --git a/framesequence/jni/Stream.h b/framesequence/jni/Stream.h
new file mode 100644
index 0000000..f8f2427
--- /dev/null
+++ b/framesequence/jni/Stream.h
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#ifndef RASTERMILL_STREAM_H
+#define RASTERMILL_STREAM_H
+
+#include <jni.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+class Stream {
+public:
+    Stream();
+    virtual ~Stream();
+
+    size_t peek(void* buffer, size_t size);
+    size_t read(void* buffer, size_t size);
+
+protected:
+    virtual size_t doRead(void* buffer, size_t size) = 0;
+
+private:
+    char* mPeekBuffer;
+    size_t mPeekSize;
+    size_t mPeekOffset;
+};
+
+class MemoryStream : public Stream {
+public:
+    MemoryStream(void* buffer, size_t size) :
+            mBuffer((char*)buffer),
+            mRemaining(size) {}
+
+protected:
+    virtual size_t doRead(void* buffer, size_t size);
+
+private:
+    char* mBuffer;
+    size_t mRemaining;
+};
+
+class FileStream : public Stream {
+public:
+    FileStream(FILE* fd) : mFd(fd) {}
+
+protected:
+    virtual size_t doRead(void* buffer, size_t size);
+
+private:
+    FILE* mFd;
+};
+
+class JavaInputStream : public Stream {
+public:
+    JavaInputStream(JNIEnv* env, jobject inputStream, jbyteArray byteArray) :
+            mEnv(env),
+            mInputStream(inputStream),
+            mByteArray(byteArray),
+            mByteArrayLength(env->GetArrayLength(byteArray)) {}
+
+protected:
+    virtual size_t doRead(void* buffer, size_t size);
+
+private:
+    JNIEnv* mEnv;
+    const jobject mInputStream;
+    const jbyteArray mByteArray;
+    const size_t mByteArrayLength;
+};
+
+jint JavaStream_OnLoad(JNIEnv* env);
+
+#endif //RASTERMILL_STREAM_H
diff --git a/framesequence/jni/utils/log.h b/framesequence/jni/utils/log.h
new file mode 100644
index 0000000..5e15f30
--- /dev/null
+++ b/framesequence/jni/utils/log.h
@@ -0,0 +1,274 @@
+/*
+ * 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.
+ */
+
+#ifndef LOG_H_
+#define LOG_H_
+
+#include <android/log.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Normally we strip ALOGV (VERBOSE messages) from release builds.
+ * You can modify this (for example with "#define LOG_NDEBUG 0"
+ * at the top of your source file) to change that behavior.
+ */
+#ifndef LOG_NDEBUG
+#ifdef NDEBUG
+#define LOG_NDEBUG 1
+#else
+#define LOG_NDEBUG 0
+#endif
+#endif
+
+/*
+ * This is the local tag used for the following simplified
+ * logging macros.  You can change this preprocessor definition
+ * before using the other macros to change the tag.
+ */
+#ifndef LOG_TAG
+#define LOG_TAG "RasterMill"
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Simplified macro to send a verbose log message using the current LOG_TAG.
+ */
+#ifndef ALOGV
+#if LOG_NDEBUG
+#define ALOGV(...)   ((void)0)
+#else
+#define ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
+#endif
+#endif
+
+#define CONDITION(cond)     (__builtin_expect((cond)!=0, 0))
+
+#ifndef ALOGV_IF
+#if LOG_NDEBUG
+#define ALOGV_IF(cond, ...)   ((void)0)
+#else
+#define ALOGV_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+#endif
+
+/*
+ * Simplified macro to send a debug log message using the current LOG_TAG.
+ */
+#ifndef ALOGD
+#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGD_IF
+#define ALOGD_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an info log message using the current LOG_TAG.
+ */
+#ifndef ALOGI
+#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGI_IF
+#define ALOGI_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send a warning log message using the current LOG_TAG.
+ */
+#ifndef ALOGW
+#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGW_IF
+#define ALOGW_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+
+/*
+ * Simplified macro to send an error log message using the current LOG_TAG.
+ */
+#ifndef ALOGE
+#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
+#endif
+
+#ifndef ALOGE_IF
+#define ALOGE_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) \
+    : (void)0 )
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * verbose priority.
+ */
+#ifndef IF_ALOGV
+#if LOG_NDEBUG
+#define IF_ALOGV() if (false)
+#else
+#define IF_ALOGV() IF_ALOG(LOG_VERBOSE, LOG_TAG)
+#endif
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * debug priority.
+ */
+#ifndef IF_ALOGD
+#define IF_ALOGD() IF_ALOG(LOG_DEBUG, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * info priority.
+ */
+#ifndef IF_ALOGI
+#define IF_ALOGI() IF_ALOG(LOG_INFO, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * warn priority.
+ */
+#ifndef IF_ALOGW
+#define IF_ALOGW() IF_ALOG(LOG_WARN, LOG_TAG)
+#endif
+
+/*
+ * Conditional based on whether the current LOG_TAG is enabled at
+ * error priority.
+ */
+#ifndef IF_ALOGE
+#define IF_ALOGE() IF_ALOG(LOG_ERROR, LOG_TAG)
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Log a fatal error.  If the given condition fails, this stops program
+ * execution like a normal assertion, but also generating the given message.
+ * It is NOT stripped from release builds.  Note that the condition test
+ * is -inverted- from the normal assert() semantics.
+ */
+#ifndef LOG_ALWAYS_FATAL_IF
+#define LOG_ALWAYS_FATAL_IF(cond, ...) \
+    ( (CONDITION(cond)) \
+    ? ((void)android_printAssert(#cond, LOG_TAG, ## __VA_ARGS__)) \
+    : (void)0 )
+#endif
+
+#ifndef LOG_ALWAYS_FATAL
+#define LOG_ALWAYS_FATAL(...) \
+    ( ((void)android_printAssert(NULL, LOG_TAG, ## __VA_ARGS__)) )
+#endif
+
+/*
+ * Versions of LOG_ALWAYS_FATAL_IF and LOG_ALWAYS_FATAL that
+ * are stripped out of release builds.
+ */
+#if LOG_NDEBUG
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) ((void)0)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) ((void)0)
+#endif
+
+#else
+
+#ifndef LOG_FATAL_IF
+#define LOG_FATAL_IF(cond, ...) LOG_ALWAYS_FATAL_IF(cond, ## __VA_ARGS__)
+#endif
+#ifndef LOG_FATAL
+#define LOG_FATAL(...) LOG_ALWAYS_FATAL(__VA_ARGS__)
+#endif
+
+#endif
+
+/*
+ * Assertion that generates a log message when the assertion fails.
+ * Stripped out of release builds.  Uses the current LOG_TAG.
+ */
+#ifndef ALOG_ASSERT
+#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ## __VA_ARGS__)
+//#define ALOG_ASSERT(cond) LOG_FATAL_IF(!(cond), "Assertion failed: " #cond)
+#endif
+
+// ---------------------------------------------------------------------
+
+/*
+ * Basic log message macro.
+ *
+ * Example:
+ *  ALOG(LOG_WARN, NULL, "Failed with error %d", errno);
+ *
+ * The second argument may be NULL or "" to indicate the "global" tag.
+ */
+#ifndef ALOG
+#define ALOG(priority, tag, ...) \
+    LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to specify a number for the priority.
+ */
+#ifndef LOG_PRI
+#define LOG_PRI(priority, tag, ...) \
+    __android_log_print(priority, tag, __VA_ARGS__)
+#endif
+
+/*
+ * Log macro that allows you to pass in a varargs ("args" is a va_list).
+ */
+#ifndef LOG_PRI_VA
+#define LOG_PRI_VA(priority, tag, fmt, args) \
+    __android_log_vprint(priority, NULL, tag, fmt, args)
+#endif
+
+/*
+ * Conditional given a desired logging priority and tag.
+ */
+#ifndef IF_ALOG
+#define IF_ALOG(priority, tag) \
+    if (__android_log_assert(ANDROID_##priority, tag))
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LOG_H_ */
diff --git a/framesequence/jni/utils/math.h b/framesequence/jni/utils/math.h
new file mode 100644
index 0000000..87f100b
--- /dev/null
+++ b/framesequence/jni/utils/math.h
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#ifndef MATH_H_
+#define MATH_H_
+
+#define max(a,b) \
+   ({ __typeof__ (a) _a = (a); \
+       __typeof__ (b) _b = (b); \
+     _a > _b ? _a : _b; })
+
+#define min(a,b) \
+   ({ __typeof__ (a) _a = (a); \
+       __typeof__ (b) _b = (b); \
+     _a < _b ? _a : _b; })
+
+#endif /* MATH_H_ */
diff --git a/framesequence/project.properties b/framesequence/project.properties
new file mode 100644
index 0000000..db721fd
--- /dev/null
+++ b/framesequence/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-8
+android.library=true
diff --git a/framesequence/samples/RastermillSamples/Android.mk b/framesequence/samples/RastermillSamples/Android.mk
new file mode 100644
index 0000000..bb8920f
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/Android.mk
@@ -0,0 +1,40 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := FrameSequenceSample
+
+# java dependency
+LOCAL_STATIC_JAVA_LIBRARIES += android-common-framesequence
+
+# native dependency
+ifneq (,$(TARGET_BUILD_APPS))
+  LOCAL_JNI_SHARED_LIBRARIES := libframesequence
+else
+  LOCAL_REQUIRED_MODULES := libframesequence
+endif
+
+# proguard for framesequence library code
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+LOCAL_SDK_VERSION := 19
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(addprefix $(LOCAL_PATH)/, res)
+LOCAL_AAPT_FLAGS := --auto-add-overlay
+LOCAL_AAPT_FLAGS += --extra-packages com.android.rastermill.samples
+
+include $(BUILD_PACKAGE)
diff --git a/framesequence/samples/RastermillSamples/AndroidManifest.xml b/framesequence/samples/RastermillSamples/AndroidManifest.xml
new file mode 100644
index 0000000..b554021
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.rastermill.samples"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="15"
+        android:targetSdkVersion="18" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name" >
+        <activity
+            android:name=".SamplesList"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".AnimatedGifTest" />
+    </application>
+
+</manifest>
diff --git a/framesequence/samples/RastermillSamples/build.xml b/framesequence/samples/RastermillSamples/build.xml
new file mode 100644
index 0000000..5e55b4e
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/build.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="RastermillSamples" default="help">
+
+    <!-- The local.properties file is created and updated by the 'android' tool.
+         It contains the path to the SDK. It should *NOT* be checked into
+         Version Control Systems. -->
+    <property file="local.properties" />
+
+    <!-- The ant.properties file can be created by you. It is only edited by the
+         'android' tool to add properties to it.
+         This is the place to change some Ant specific build properties.
+         Here are some properties you may want to change/update:
+
+         source.dir
+             The name of the source directory. Default is 'src'.
+         out.dir
+             The name of the output directory. Default is 'bin'.
+
+         For other overridable properties, look at the beginning of the rules
+         files in the SDK, at tools/ant/build.xml
+
+         Properties related to the SDK location or the project target should
+         be updated using the 'android' tool with the 'update' action.
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems.
+
+         -->
+    <property file="ant.properties" />
+
+    <!-- if sdk.dir was not set from one of the property file, then
+         get it from the ANDROID_HOME env var.
+         This must be done before we load project.properties since
+         the proguard config can use sdk.dir -->
+    <property environment="env" />
+    <condition property="sdk.dir" value="${env.ANDROID_HOME}">
+        <isset property="env.ANDROID_HOME" />
+    </condition>
+
+    <!-- The project.properties file is created and updated by the 'android'
+         tool, as well as ADT.
+
+         This contains project specific properties such as project target, and library
+         dependencies. Lower level build properties are stored in ant.properties
+         (or in .classpath for Eclipse projects).
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems. -->
+    <loadproperties srcFile="project.properties" />
+
+    <!-- quick check on sdk.dir -->
+    <fail
+            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
+            unless="sdk.dir"
+    />
+
+    <target name="-pre-build">
+        <ant dir="../../" target="release" inheritAll="false" />
+        <copy todir="libs">
+            <fileset dir="../../exported_libs" />
+        </copy>
+    </target>
+
+    <!--
+        Import per project custom build rules if present at the root of the project.
+        This is the place to put custom intermediary targets such as:
+            -pre-build
+            -pre-compile
+            -post-compile (This is typically used for code obfuscation.
+                           Compiled code location: ${out.classes.absolute.dir}
+                           If this is not done in place, override ${out.dex.input.absolute.dir})
+            -post-package
+            -post-build
+            -pre-clean
+    -->
+    <import file="custom_rules.xml" optional="true" />
+
+    <!-- Import the actual build file.
+
+         To customize existing targets, there are two options:
+         - Customize only one target:
+             - copy/paste the target into this file, *before* the
+               <import> task.
+             - customize it to your needs.
+         - Customize the whole content of build.xml
+             - copy/paste the content of the rules files (minus the top node)
+               into this file, replacing the <import> task.
+             - customize to your needs.
+
+         ***********************
+         ****** IMPORTANT ******
+         ***********************
+         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
+         in order to avoid having your file be overridden by tools such as "android update project"
+    -->
+    <!-- version-tag: 1 -->
+    <import file="${sdk.dir}/tools/ant/build.xml" />
+
+</project>
diff --git a/framesequence/samples/RastermillSamples/proguard.flags b/framesequence/samples/RastermillSamples/proguard.flags
new file mode 100644
index 0000000..4acde2d
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/proguard.flags
@@ -0,0 +1,3 @@
+-keep class android.support.rastermill.** {
+  *;
+}
diff --git a/framesequence/samples/RastermillSamples/project.properties b/framesequence/samples/RastermillSamples/project.properties
new file mode 100644
index 0000000..ce39f2d
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/project.properties
@@ -0,0 +1,14 @@
+# 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-18
diff --git a/framesequence/samples/RastermillSamples/res/drawable-hdpi/ic_launcher.png b/framesequence/samples/RastermillSamples/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/framesequence/samples/RastermillSamples/res/drawable-mdpi/ic_launcher.png b/framesequence/samples/RastermillSamples/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/framesequence/samples/RastermillSamples/res/drawable-xhdpi/ic_launcher.png b/framesequence/samples/RastermillSamples/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/framesequence/samples/RastermillSamples/res/layout/basic_test_activity.xml b/framesequence/samples/RastermillSamples/res/layout/basic_test_activity.xml
new file mode 100644
index 0000000..0b9a2df
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/layout/basic_test_activity.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+    <ImageView
+            android:id="@+id/imageview"
+            android:layout_width="match_parent"
+            android:layout_height="300dp" />
+    <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+        <Button
+                android:id="@+id/start"
+                android:text="@string/start"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+        <Button
+                android:id="@+id/stop"
+                android:text="@string/stop"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+        <Button
+                android:id="@+id/vis"
+                android:text="@string/vis"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+        <Button
+                android:id="@+id/invis"
+                android:text="@string/invis"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"/>
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/framesequence/samples/RastermillSamples/res/raw/animated.gif b/framesequence/samples/RastermillSamples/res/raw/animated.gif
new file mode 100644
index 0000000..51baf15
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/raw/animated.gif
Binary files differ
diff --git a/framesequence/samples/RastermillSamples/res/values/strings.xml b/framesequence/samples/RastermillSamples/res/values/strings.xml
new file mode 100644
index 0000000..811c979
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/values/strings.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- NOTE: all strings should be marked as translatable=false,
+         since this sample app is for testing, and won't be shipped -->
+
+    <string name="app_name" translatable="false">Rastermill Samples</string>
+    <string name="action_settings" translatable="false">Settings</string>
+
+    <string name="start" translatable="false">start</string>
+    <string name="stop" translatable="false">stop</string>
+    <string name="vis" translatable="false">vis</string>
+    <string name="invis" translatable="false">invis</string>
+
+</resources>
diff --git a/framesequence/samples/RastermillSamples/res/values/styles.xml b/framesequence/samples/RastermillSamples/res/values/styles.xml
new file mode 100644
index 0000000..737bdc3
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/res/values/styles.xml
@@ -0,0 +1,7 @@
+<resources>
+
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+    </style>
+
+</resources>
diff --git a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java
new file mode 100644
index 0000000..2328975
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/AnimatedGifTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.rastermill.samples;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.support.rastermill.FrameSequence;
+import android.support.rastermill.FrameSequenceDrawable;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import java.io.InputStream;
+import java.util.HashSet;
+
+public class AnimatedGifTest extends Activity {
+    FrameSequenceDrawable mDrawable;
+
+    // This provider is entirely unnecessary, just here to validate the acquire/release process
+    private class CheckingProvider implements FrameSequenceDrawable.BitmapProvider {
+        HashSet<Bitmap> mBitmaps = new HashSet<Bitmap>();
+        @Override
+        public Bitmap acquireBitmap(int minWidth, int minHeight) {
+            Bitmap bitmap =
+                    Bitmap.createBitmap(minWidth + 1, minHeight + 4, Bitmap.Config.ARGB_8888);
+            mBitmaps.add(bitmap);
+            return bitmap;
+        }
+
+        @Override
+        public void releaseBitmap(Bitmap bitmap) {
+            if (!mBitmaps.contains(bitmap)) throw new IllegalStateException();
+            mBitmaps.remove(bitmap);
+            bitmap.recycle();
+        }
+
+        public boolean isEmpty() {
+            return mBitmaps.isEmpty();
+        }
+    }
+
+    final CheckingProvider mProvider = new CheckingProvider();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.basic_test_activity);
+        findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mDrawable.start();
+            }
+        });
+        findViewById(R.id.stop).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mDrawable.stop();
+            }
+        });
+        findViewById(R.id.vis).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mDrawable.setVisible(true, true);
+            }
+        });
+        findViewById(R.id.invis).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mDrawable.setVisible(false, true);
+            }
+        });
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        ImageView imageView = (ImageView) findViewById(R.id.imageview);
+        InputStream is = getResources().openRawResource(R.raw.animated);
+
+        FrameSequence fs = FrameSequence.decodeStream(is);
+        mDrawable = new FrameSequenceDrawable(fs, mProvider);
+        mDrawable.setOnFinishedListener(new FrameSequenceDrawable.OnFinishedListener() {
+            @Override
+            public void onFinished(FrameSequenceDrawable drawable) {
+                Toast.makeText(getApplicationContext(),
+                        "THE ANIMATION HAS FINISHED", Toast.LENGTH_SHORT).show();
+            }
+        });
+        imageView.setImageDrawable(mDrawable);
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        ImageView imageView = (ImageView) findViewById(R.id.imageview);
+
+        mDrawable.destroy();
+        if (!mProvider.isEmpty()) throw new IllegalStateException("All bitmaps not recycled");
+
+        mDrawable = null;
+        imageView.setImageDrawable(null);
+
+    }
+}
diff --git a/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/SamplesList.java b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/SamplesList.java
new file mode 100644
index 0000000..0447537
--- /dev/null
+++ b/framesequence/samples/RastermillSamples/src/com/android/rastermill/samples/SamplesList.java
@@ -0,0 +1,61 @@
+/*
+ * 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.rastermill.samples;
+
+import android.app.ListActivity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class SamplesList extends ListActivity {
+
+    static final String KEY_NAME = "name";
+    static final String KEY_CLASS = "clazz";
+
+    static Map<String,?> makeSample(String name, Class<?> activity) {
+        Map<String,Object> ret = new HashMap<String,Object>();
+        ret.put(KEY_NAME, name);
+        ret.put(KEY_CLASS, activity);
+        return ret;
+    }
+
+    @SuppressWarnings("serial")
+    static final ArrayList<Map<String,?>> SAMPLES = new ArrayList<Map<String,?>>() {{
+        add(makeSample("Animation Test", AnimatedGifTest.class));
+    }};
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setListAdapter(new SimpleAdapter(this, SAMPLES,
+                android.R.layout.simple_list_item_1, new String[] { KEY_NAME },
+                new int[] { android.R.id.text1 }));
+    }
+
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        Class<?> clazz = (Class<?>) SAMPLES.get(position).get(KEY_CLASS);
+        startActivity(new Intent(this, clazz));
+    }
+
+}
diff --git a/framesequence/src/android/support/rastermill/FrameSequence.java b/framesequence/src/android/support/rastermill/FrameSequence.java
new file mode 100644
index 0000000..d2bd128
--- /dev/null
+++ b/framesequence/src/android/support/rastermill/FrameSequence.java
@@ -0,0 +1,136 @@
+/*
+ * 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 android.support.rastermill;
+
+import android.graphics.Bitmap;
+
+import java.io.InputStream;
+
+public class FrameSequence {
+    static {
+        System.loadLibrary("framesequence");
+    }
+
+    private final long mNativeFrameSequence;
+    private final int mWidth;
+    private final int mHeight;
+    private final boolean mOpaque;
+    private final int mFrameCount;
+    private final int mDefaultLoopCount;
+
+    public int getWidth() { return mWidth; }
+    public int getHeight() { return mHeight; }
+    public boolean isOpaque() { return mOpaque; }
+    public int getFrameCount() { return mFrameCount; }
+    public int getDefaultLoopCount() { return mDefaultLoopCount; }
+
+    private static native FrameSequence nativeDecodeByteArray(byte[] data, int offset, int length);
+    private static native FrameSequence nativeDecodeStream(InputStream is, byte[] tempStorage);
+    private static native void nativeDestroyFrameSequence(long nativeFrameSequence);
+    private static native long nativeCreateState(long nativeFrameSequence);
+    private static native void nativeDestroyState(long nativeState);
+    private static native long nativeGetFrame(long nativeState, int frameNr,
+            Bitmap output, int previousFrameNr);
+
+    @SuppressWarnings("unused") // called by native
+    private FrameSequence(long nativeFrameSequence, int width, int height,
+                          boolean opaque, int frameCount, int defaultLoopCount) {
+        mNativeFrameSequence = nativeFrameSequence;
+        mWidth = width;
+        mHeight = height;
+        mOpaque = opaque;
+        mFrameCount = frameCount;
+        mDefaultLoopCount = defaultLoopCount;
+    }
+
+    public static FrameSequence decodeByteArray(byte[] data) {
+        return decodeByteArray(data, 0, data.length);
+    }
+
+    public static FrameSequence decodeByteArray(byte[] data, int offset, int length) {
+        if (data == null) throw new IllegalArgumentException();
+        if (offset < 0 || length < 0 || (offset + length > data.length)) {
+            throw new IllegalArgumentException("invalid offset/length parameters");
+        }
+        return nativeDecodeByteArray(data, offset, length);
+    }
+
+    public static FrameSequence decodeStream(InputStream stream) {
+        if (stream == null) throw new IllegalArgumentException();
+        byte[] tempStorage = new byte[16 * 1024]; // TODO: use buffer pool
+        return nativeDecodeStream(stream, tempStorage);
+    }
+
+    State createState() {
+        if (mNativeFrameSequence == 0) {
+            throw new IllegalStateException("attempted to use incorrectly built FrameSequence");
+        }
+
+        long nativeState = nativeCreateState(mNativeFrameSequence);
+        if (nativeState == 0) {
+            return null;
+        }
+        return new State(nativeState);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mNativeFrameSequence != 0) nativeDestroyFrameSequence(mNativeFrameSequence);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Playback state used when moving frames forward in a frame sequence.
+     *
+     * Note that this doesn't require contiguous frames to be rendered, it just stores
+     * information (in the case of gif, a recall buffer) that will be used to construct
+     * frames based upon data recorded before previousFrameNr.
+     *
+     * Note: {@link #destroy()} *must* be called before the object is GC'd to free native resources
+     *
+     * Note: State holds a native ref to its FrameSequence instance, so its FrameSequence should
+     * remain ref'd while it is in use
+     */
+    static class State {
+        private long mNativeState;
+
+        public State(long nativeState) {
+            mNativeState = nativeState;
+        }
+
+        public void destroy() {
+            if (mNativeState != 0) {
+                nativeDestroyState(mNativeState);
+                mNativeState = 0;
+            }
+        }
+
+        // TODO: consider adding alternate API for drawing into a SurfaceTexture
+        public long getFrame(int frameNr, Bitmap output, int previousFrameNr) {
+            if (output == null || output.getConfig() != Bitmap.Config.ARGB_8888) {
+                throw new IllegalArgumentException("Bitmap passed must be non-null and ARGB_8888");
+            }
+            if (mNativeState == 0) {
+                throw new IllegalStateException("attempted to draw destroyed FrameSequenceState");
+            }
+            return nativeGetFrame(mNativeState, frameNr, output, previousFrameNr);
+        }
+    }
+}
diff --git a/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java
new file mode 100644
index 0000000..cda0cfe
--- /dev/null
+++ b/framesequence/src/android/support/rastermill/FrameSequenceDrawable.java
@@ -0,0 +1,417 @@
+/*
+ * 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 android.support.rastermill;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.SystemClock;
+
+public class FrameSequenceDrawable extends Drawable implements Animatable, Runnable {
+    private static final Object sLock = new Object();
+    private static HandlerThread sDecodingThread;
+    private static Handler sDecodingThreadHandler;
+    private static void initializeDecodingThread() {
+        synchronized (sLock) {
+            if (sDecodingThread != null) return;
+
+            sDecodingThread = new HandlerThread("FrameSequence decoding thread",
+                    Process.THREAD_PRIORITY_BACKGROUND);
+            sDecodingThread.start();
+            sDecodingThreadHandler = new Handler(sDecodingThread.getLooper());
+        }
+    }
+
+    public static interface OnFinishedListener {
+        /**
+         * Called when a FrameSequenceDrawable has finished looping.
+         *
+         * Note that this is will not be called if the drawable is explicitly
+         * stopped, or marked invisible.
+         */
+        public abstract void onFinished(FrameSequenceDrawable drawable);
+    }
+
+    public static interface BitmapProvider {
+        /**
+         * Called by FrameSequenceDrawable to aquire an 8888 Bitmap with minimum dimensions.
+         */
+        public abstract Bitmap acquireBitmap(int minWidth, int minHeight);
+
+        /**
+         * Called by FrameSequenceDrawable to release a Bitmap it no longer needs. The Bitmap
+         * will no longer be used at all by the drawable, so it is safe to reuse elsewhere.
+         *
+         * This method may be called by FrameSequenceDrawable on any thread.
+         */
+        public abstract void releaseBitmap(Bitmap bitmap);
+    }
+
+    private static BitmapProvider sAllocatingBitmapProvider = new BitmapProvider() {
+        @Override
+        public Bitmap acquireBitmap(int minWidth, int minHeight) {
+            return Bitmap.createBitmap(minWidth, minHeight, Bitmap.Config.ARGB_8888);
+        }
+
+        @Override
+        public void releaseBitmap(Bitmap bitmap) {
+            bitmap.recycle();
+        }
+    };
+
+    /**
+     * Register a callback to be invoked when a FrameSequenceDrawable finishes looping.
+     *
+     * @see #setLoopBehavior(int)
+     */
+    public void setOnFinishedListener(OnFinishedListener onFinishedListener) {
+        mOnFinishedListener = onFinishedListener;
+    }
+
+    /**
+     * Loop only once.
+     */
+    public static final int LOOP_ONCE = 1;
+
+    /**
+     * Loop continuously. The OnFinishedListener will never be called.
+     */
+    public static final int LOOP_INF = 2;
+
+    /**
+     * Use loop count stored in source data, or LOOP_ONCE if not present.
+     */
+    public static final int LOOP_DEFAULT = 3;
+
+    /**
+     * Define looping behavior of frame sequence.
+     *
+     * Must be one of LOOP_ONCE, LOOP_INF, or LOOP_DEFAULT
+     */
+    public void setLoopBehavior(int loopBehavior) {
+        mLoopBehavior = loopBehavior;
+    }
+
+    private final FrameSequence mFrameSequence;
+    private final FrameSequence.State mFrameSequenceState;
+
+    private final Paint mPaint;
+    private final Rect mSrcRect;
+
+    //Protects the fields below
+    private final Object mLock = new Object();
+
+    private final BitmapProvider mBitmapProvider;
+    private boolean mDestroyed = false;
+    private Bitmap mFrontBitmap;
+    private Bitmap mBackBitmap;
+
+    private static final int STATE_SCHEDULED = 1;
+    private static final int STATE_DECODING = 2;
+    private static final int STATE_WAITING_TO_SWAP = 3;
+    private static final int STATE_READY_TO_SWAP = 4;
+
+    private int mState;
+    private int mCurrentLoop;
+    private int mLoopBehavior = LOOP_DEFAULT;
+
+    private long mLastSwap;
+    private long mNextSwap;
+    private int mNextFrameToDecode;
+    private OnFinishedListener mOnFinishedListener;
+
+    /**
+     * Runs on decoding thread, only modifies mBackBitmap's pixels
+     */
+    private Runnable mDecodeRunnable = new Runnable() {
+        @Override
+        public void run() {
+            int nextFrame;
+            Bitmap bitmap;
+            synchronized (mLock) {
+                if (mDestroyed) return;
+
+                nextFrame = mNextFrameToDecode;
+                if (nextFrame < 0) {
+                    return;
+                }
+                bitmap = mBackBitmap;
+                mState = STATE_DECODING;
+            }
+            int lastFrame = nextFrame - 2;
+            long invalidateTimeMs = mFrameSequenceState.getFrame(nextFrame, bitmap, lastFrame);
+
+            synchronized (mLock) {
+                if (mNextFrameToDecode < 0 || mState != STATE_DECODING) return;
+                mNextSwap = invalidateTimeMs + mLastSwap;
+
+                mState = STATE_WAITING_TO_SWAP;
+            }
+            scheduleSelf(FrameSequenceDrawable.this, mNextSwap);
+        }
+    };
+
+    private Runnable mCallbackRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (mOnFinishedListener != null) {
+                mOnFinishedListener.onFinished(FrameSequenceDrawable.this);
+            }
+        }
+    };
+
+    private static Bitmap acquireAndValidateBitmap(BitmapProvider bitmapProvider,
+            int minWidth, int minHeight) {
+        Bitmap bitmap = bitmapProvider.acquireBitmap(minWidth, minHeight);
+
+        if (bitmap.getWidth() < minWidth
+                || bitmap.getHeight() < minHeight
+                || bitmap.getConfig() != Bitmap.Config.ARGB_8888) {
+            throw new IllegalArgumentException("Invalid bitmap provided");
+        }
+
+        return bitmap;
+    }
+
+    public FrameSequenceDrawable(FrameSequence frameSequence) {
+        this(frameSequence, sAllocatingBitmapProvider);
+    }
+
+    public FrameSequenceDrawable(FrameSequence frameSequence, BitmapProvider bitmapProvider) {
+        if (frameSequence == null || bitmapProvider == null) throw new IllegalArgumentException();
+
+        mFrameSequence = frameSequence;
+        mFrameSequenceState = frameSequence.createState();
+        final int width = frameSequence.getWidth();
+        final int height = frameSequence.getHeight();
+
+        mBitmapProvider = bitmapProvider;
+        mFrontBitmap = acquireAndValidateBitmap(bitmapProvider, width, height);
+        mBackBitmap = acquireAndValidateBitmap(bitmapProvider, width, height);
+        mSrcRect = new Rect(0, 0, width, height);
+        mPaint = new Paint();
+        mPaint.setFilterBitmap(true);
+
+        mLastSwap = 0;
+
+        mNextFrameToDecode = -1;
+        mFrameSequenceState.getFrame(0, mFrontBitmap, -1);
+        initializeDecodingThread();
+    }
+
+    private void checkDestroyedLocked() {
+        if (mDestroyed) {
+            throw new IllegalStateException("Cannot perform operation on recycled drawable");
+        }
+    }
+
+    public boolean isDestroyed() {
+        synchronized (mLock) {
+            return mDestroyed;
+        }
+    }
+
+    /**
+     * Marks the drawable as permanently recycled (and thus unusable), and releases any owned
+     * Bitmaps drawable to its BitmapProvider, if attached.
+     *
+     * If no BitmapProvider is attached to the drawable, recycle() is called on the Bitmaps.
+     */
+    public void destroy() {
+        destroy(mBitmapProvider);
+    }
+
+    private void destroy(BitmapProvider bitmapProvider) {
+        if (bitmapProvider == null) {
+            throw new IllegalStateException("BitmapProvider must be non-null");
+        }
+
+        Bitmap bitmapToReleaseA;
+        Bitmap bitmapToReleaseB;
+        synchronized (mLock) {
+            checkDestroyedLocked();
+
+            bitmapToReleaseA = mFrontBitmap;
+            bitmapToReleaseB = mBackBitmap;
+
+            mFrontBitmap = null;
+            mBackBitmap = null;
+            mDestroyed = true;
+        }
+
+        // For simplicity and safety, we don't destroy the state object here
+        bitmapProvider.releaseBitmap(bitmapToReleaseA);
+        bitmapProvider.releaseBitmap(bitmapToReleaseB);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            mFrameSequenceState.destroy();
+            if (!mDestroyed) {
+                destroy();
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        synchronized (mLock) {
+            checkDestroyedLocked();
+            if (mState == STATE_WAITING_TO_SWAP) {
+                // may have failed to schedule mark ready runnable,
+                // so go ahead and swap if swapping is due
+                if (mNextSwap - SystemClock.uptimeMillis() <= 0) {
+                    mState = STATE_READY_TO_SWAP;
+                }
+            }
+
+            if (isRunning() && mState == STATE_READY_TO_SWAP) {
+                // Because draw has occurred, the view system is guaranteed to no longer hold a
+                // reference to the old mFrontBitmap, so we now use it to produce the next frame
+                Bitmap tmp = mBackBitmap;
+                mBackBitmap = mFrontBitmap;
+                mFrontBitmap = tmp;
+
+                mLastSwap = SystemClock.uptimeMillis();
+
+                boolean continueLooping = true;
+                if (mNextFrameToDecode == mFrameSequence.getFrameCount() - 1) {
+                    mCurrentLoop++;
+                    if ((mLoopBehavior == LOOP_ONCE && mCurrentLoop == 1) ||
+                            (mLoopBehavior == LOOP_DEFAULT && mCurrentLoop == mFrameSequence.getDefaultLoopCount())) {
+                        continueLooping = false;
+                    }
+                }
+
+                if (continueLooping) {
+                    scheduleDecodeLocked();
+                } else {
+                    scheduleSelf(mCallbackRunnable, 0);
+                }
+            }
+        }
+
+        canvas.drawBitmap(mFrontBitmap, mSrcRect, getBounds(), mPaint);
+    }
+
+    private void scheduleDecodeLocked() {
+        mState = STATE_SCHEDULED;
+        mNextFrameToDecode = (mNextFrameToDecode + 1) % mFrameSequence.getFrameCount();
+        sDecodingThreadHandler.post(mDecodeRunnable);
+    }
+
+    @Override
+    public void run() {
+        // set ready to swap
+        synchronized (mLock) {
+            if (mState != STATE_WAITING_TO_SWAP || mNextFrameToDecode < 0) return;
+            mState = STATE_READY_TO_SWAP;
+        }
+        invalidateSelf();
+    }
+
+    @Override
+    public void start() {
+        if (!isRunning()) {
+            synchronized (mLock) {
+                checkDestroyedLocked();
+                if (mState == STATE_SCHEDULED) return; // already scheduled
+                mCurrentLoop = 0;
+                scheduleDecodeLocked();
+            }
+        }
+    }
+
+    @Override
+    public void stop() {
+        if (isRunning()) {
+            unscheduleSelf(this);
+        }
+    }
+
+    @Override
+    public boolean isRunning() {
+        synchronized (mLock) {
+            return mNextFrameToDecode > -1 && !mDestroyed;
+        }
+    }
+
+    @Override
+    public void unscheduleSelf(Runnable what) {
+        synchronized (mLock) {
+            mNextFrameToDecode = -1;
+        }
+        super.unscheduleSelf(what);
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        boolean changed = super.setVisible(visible, restart);
+
+        if (!visible) {
+            stop();
+        } else if (restart || changed) {
+            stop();
+            start();
+        }
+
+        return changed;
+    }
+
+    // drawing properties
+
+    @Override
+    public void setFilterBitmap(boolean filter) {
+        mPaint.setFilterBitmap(filter);
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mPaint.setAlpha(alpha);
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mPaint.setColorFilter(colorFilter);
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mFrameSequence.getWidth();
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mFrameSequence.getHeight();
+    }
+
+    @Override
+    public int getOpacity() {
+        return mFrameSequence.isOpaque() ? PixelFormat.OPAQUE : PixelFormat.TRANSPARENT;
+    }
+}
diff --git a/variablespeed/jni/Android.mk b/variablespeed/jni/Android.mk
index 08de535..0a7818a 100644
--- a/variablespeed/jni/Android.mk
+++ b/variablespeed/jni/Android.mk
@@ -46,9 +46,4 @@
     libcutils \
     liblog \
 
-LOCAL_LDLIBS := \
-    -lOpenSLES \
-    -llog \
-    -landroid \
-
 include $(BUILD_SHARED_LIBRARY)
diff --git a/variablespeed/jni/jni_entry.cc b/variablespeed/jni/jni_entry.cc
index 368d230..93c12ba 100644
--- a/variablespeed/jni/jni_entry.cc
+++ b/variablespeed/jni/jni_entry.cc
@@ -39,7 +39,7 @@
 };
 
 extern "C" {
-JNI_METHOD(playFileDescriptor, void) (JNIEnv*, jclass, int fd, jlong offset,
+JNI_METHOD(playFileDescriptor, void) (JNIEnv*, jclass, jint fd, jlong offset,
     jlong length) {
   MethodLog _("playFileDescriptor");
   AudioEngine::GetEngine()->PlayFileDescriptor(fd, offset, length);
@@ -52,7 +52,7 @@
   AudioEngine::GetEngine()->PlayUri(utf8);
 }
 
-JNI_METHOD(setVariableSpeed, void) (JNIEnv*, jclass, float speed) {
+JNI_METHOD(setVariableSpeed, void) (JNIEnv*, jclass, jfloat speed) {
   MethodLog _("setVariableSpeed");
   AudioEngine::GetEngine()->SetVariableSpeed(speed);
 }
@@ -67,19 +67,19 @@
   AudioEngine::GetEngine()->RequestStop();
 }
 
-JNI_METHOD(getCurrentPosition, int) (JNIEnv*, jclass) {
+JNI_METHOD(getCurrentPosition, jint) (JNIEnv*, jclass) {
   return AudioEngine::GetEngine()->GetCurrentPosition();
 }
 
-JNI_METHOD(getTotalDuration, int) (JNIEnv*, jclass) {
+JNI_METHOD(getTotalDuration, jint) (JNIEnv*, jclass) {
   return AudioEngine::GetEngine()->GetTotalDuration();
 }
 
 JNI_METHOD(initializeEngine, void) (JNIEnv*, jclass,
-    int targetFrames, float windowDuration,
-    float windowOverlapDuration, size_t maxPlayBufferCount,
-    float initialRate, size_t decodeInitialSize, size_t decodeMaxSize,
-    size_t startPositionMillis, int audioStreamType) {
+    jint targetFrames, jfloat windowDuration,
+    jfloat windowOverlapDuration, jint maxPlayBufferCount,
+    jfloat initialRate, jint decodeInitialSize, jint decodeMaxSize,
+    jint startPositionMillis, jint audioStreamType) {
   MethodLog _("initializeEngine");
   AudioEngine::SetEngine(new AudioEngine(targetFrames,
       windowDuration, windowOverlapDuration, maxPlayBufferCount, initialRate,
diff --git a/variablespeed/jni/variablespeed.cc b/variablespeed/jni/variablespeed.cc
index 73ac609..b5d9067 100644
--- a/variablespeed/jni/variablespeed.cc
+++ b/variablespeed/jni/variablespeed.cc
@@ -564,7 +564,7 @@
 }
 
 static void CreateAndRealizeAudioPlayer(SLuint32 slSampleRate,
-    size_t channelCount, SLuint32 slChannels, SLint32 audioStreamType, SLObjectItf &outputMix,
+    SLuint32 channelCount, SLuint32 slChannels, SLint32 audioStreamType, SLObjectItf &outputMix,
     SLObjectItf &audioPlayer, SLEngineItf &engineInterface) {
   // Define the source and sink for the audio player: comes from a buffer queue
   // and goes to the output mix.
diff --git a/variablespeed/jni/variablespeed.h b/variablespeed/jni/variablespeed.h
index 07cba0f..cf856da 100644
--- a/variablespeed/jni/variablespeed.h
+++ b/variablespeed/jni/variablespeed.h
@@ -86,7 +86,7 @@
   bool HasSampleRateAndChannels();
   SLuint32 GetSLSampleRate();
   SLuint32 GetSLChannels();
-  size_t GetChannelCount();
+  SLuint32 GetChannelCount();
 
   // The single global audio engine instance.
   static AudioEngine* audioEngine_;