Merge "Optimize photo cache invalidation a bit"
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 43fd693..e604de1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -614,7 +614,8 @@
                     android:resource="@xml/social_widget_info" />
         </receiver>
 
-        <receiver android:name=".calllog.CallLogReceiver">
+        <receiver android:name=".calllog.CallLogReceiver"
+            android:enabled="@*android:bool/config_voice_capable">
             <intent-filter>
                 <action android:name="android.intent.action.NEW_VOICEMAIL" />
                 <data
diff --git a/res/drawable-hdpi/ab_bottom_opaque_dark_holo.9.png b/res/drawable-hdpi/ab_bottom_opaque_dark_holo.9.png
deleted file mode 100644
index 4991dff..0000000
--- a/res/drawable-hdpi/ab_bottom_opaque_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/bg_blk_search_contact.9.png b/res/drawable-hdpi/bg_blk_search_contact.9.png
deleted file mode 100644
index db83477..0000000
--- a/res/drawable-hdpi/bg_blk_search_contact.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_contacts.png b/res/drawable-hdpi/ic_launcher_contacts.png
deleted file mode 100644
index f2657dc..0000000
--- a/res/drawable-hdpi/ic_launcher_contacts.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_phone.png b/res/drawable-hdpi/ic_launcher_phone.png
deleted file mode 100644
index 37ffb9c..0000000
--- a/res/drawable-hdpi/ic_launcher_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_shortcut_contact.png b/res/drawable-hdpi/ic_launcher_shortcut_contact.png
deleted file mode 100644
index 2dc1014..0000000
--- a/res/drawable-hdpi/ic_launcher_shortcut_contact.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_shortcut_directdial.png b/res/drawable-hdpi/ic_launcher_shortcut_directdial.png
deleted file mode 100644
index b5c0dd9..0000000
--- a/res/drawable-hdpi/ic_launcher_shortcut_directdial.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_launcher_shortcut_directmessage.png b/res/drawable-hdpi/ic_launcher_shortcut_directmessage.png
deleted file mode 100644
index 10720e4..0000000
--- a/res/drawable-hdpi/ic_launcher_shortcut_directmessage.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_add_contact_holo_light.png b/res/drawable-hdpi/ic_menu_add_contact_holo_light.png
deleted file mode 100644
index 616dd2a..0000000
--- a/res/drawable-hdpi/ic_menu_add_contact_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/ic_menu_contacts_holo_light.png b/res/drawable-hdpi/ic_menu_contacts_holo_light.png
deleted file mode 100644
index 6327545..0000000
--- a/res/drawable-hdpi/ic_menu_contacts_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/list_item_divider.9.png b/res/drawable-hdpi/list_item_divider.9.png
deleted file mode 100644
index 60e2cb2..0000000
--- a/res/drawable-hdpi/list_item_divider.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/list_pressed_holo.9.png b/res/drawable-hdpi/list_pressed_holo.9.png
deleted file mode 100644
index dd183c0..0000000
--- a/res/drawable-hdpi/list_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/magnifying_glass.png b/res/drawable-hdpi/magnifying_glass.png
deleted file mode 100755
index ac88fb4..0000000
--- a/res/drawable-hdpi/magnifying_glass.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/tab_focused.9.png b/res/drawable-hdpi/tab_focused.9.png
deleted file mode 100644
index a65b8f5..0000000
--- a/res/drawable-hdpi/tab_focused.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/tab_left_arrow.png b/res/drawable-hdpi/tab_left_arrow.png
deleted file mode 100644
index c2274f1..0000000
--- a/res/drawable-hdpi/tab_left_arrow.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/tab_pressed.9.png b/res/drawable-hdpi/tab_pressed.9.png
deleted file mode 100644
index 0f90065..0000000
--- a/res/drawable-hdpi/tab_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/tab_right_arrow.png b/res/drawable-hdpi/tab_right_arrow.png
deleted file mode 100644
index 8a847e0..0000000
--- a/res/drawable-hdpi/tab_right_arrow.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/tab_selected.9.png b/res/drawable-hdpi/tab_selected.9.png
deleted file mode 100644
index 08a39ff..0000000
--- a/res/drawable-hdpi/tab_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-hdpi/tab_unselected.9.png b/res/drawable-hdpi/tab_unselected.9.png
deleted file mode 100644
index 6150b5b..0000000
--- a/res/drawable-hdpi/tab_unselected.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ab_bottom_opaque_dark_holo.9.png b/res/drawable-mdpi/ab_bottom_opaque_dark_holo.9.png
deleted file mode 100644
index 15bef5d..0000000
--- a/res/drawable-mdpi/ab_bottom_opaque_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/bg_blk_search_contact.9.png b/res/drawable-mdpi/bg_blk_search_contact.9.png
deleted file mode 100644
index 2694b5c..0000000
--- a/res/drawable-mdpi/bg_blk_search_contact.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_contacts.png b/res/drawable-mdpi/ic_launcher_contacts.png
deleted file mode 100644
index 6b06cac..0000000
--- a/res/drawable-mdpi/ic_launcher_contacts.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_phone.png b/res/drawable-mdpi/ic_launcher_phone.png
deleted file mode 100644
index 5ef8c40..0000000
--- a/res/drawable-mdpi/ic_launcher_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_shortcut_contact.png b/res/drawable-mdpi/ic_launcher_shortcut_contact.png
deleted file mode 100644
index bce245c..0000000
--- a/res/drawable-mdpi/ic_launcher_shortcut_contact.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_shortcut_directdial.png b/res/drawable-mdpi/ic_launcher_shortcut_directdial.png
deleted file mode 100644
index e4649cf..0000000
--- a/res/drawable-mdpi/ic_launcher_shortcut_directdial.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_launcher_shortcut_directmessage.png b/res/drawable-mdpi/ic_launcher_shortcut_directmessage.png
deleted file mode 100644
index 4be34e5..0000000
--- a/res/drawable-mdpi/ic_launcher_shortcut_directmessage.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_add_contact_holo_light.png b/res/drawable-mdpi/ic_menu_add_contact_holo_light.png
deleted file mode 100644
index 0cfb71b..0000000
--- a/res/drawable-mdpi/ic_menu_add_contact_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/ic_menu_contacts_holo_light.png b/res/drawable-mdpi/ic_menu_contacts_holo_light.png
deleted file mode 100644
index 4a15754..0000000
--- a/res/drawable-mdpi/ic_menu_contacts_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/list_item_divider.9.png b/res/drawable-mdpi/list_item_divider.9.png
deleted file mode 100644
index 60e2cb2..0000000
--- a/res/drawable-mdpi/list_item_divider.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/list_pressed_holo.9.png b/res/drawable-mdpi/list_pressed_holo.9.png
deleted file mode 100644
index 9358dd8..0000000
--- a/res/drawable-mdpi/list_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/magnifying_glass.png b/res/drawable-mdpi/magnifying_glass.png
deleted file mode 100755
index 2592ae0..0000000
--- a/res/drawable-mdpi/magnifying_glass.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/tab_focused.9.png b/res/drawable-mdpi/tab_focused.9.png
deleted file mode 100644
index 6190694..0000000
--- a/res/drawable-mdpi/tab_focused.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/tab_left_arrow.png b/res/drawable-mdpi/tab_left_arrow.png
deleted file mode 100644
index e35d58d..0000000
--- a/res/drawable-mdpi/tab_left_arrow.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/tab_pressed.9.png b/res/drawable-mdpi/tab_pressed.9.png
deleted file mode 100644
index dbada51..0000000
--- a/res/drawable-mdpi/tab_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/tab_right_arrow.png b/res/drawable-mdpi/tab_right_arrow.png
deleted file mode 100644
index 8acbba0..0000000
--- a/res/drawable-mdpi/tab_right_arrow.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/tab_selected.9.png b/res/drawable-mdpi/tab_selected.9.png
deleted file mode 100644
index b3c9fb1..0000000
--- a/res/drawable-mdpi/tab_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/tab_unselected.9.png b/res/drawable-mdpi/tab_unselected.9.png
deleted file mode 100644
index a79fa2a..0000000
--- a/res/drawable-mdpi/tab_unselected.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ab_bottom_opaque_dark_holo.9.png b/res/drawable-xhdpi/ab_bottom_opaque_dark_holo.9.png
deleted file mode 100644
index 454e8b4..0000000
--- a/res/drawable-xhdpi/ab_bottom_opaque_dark_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/bg_blk_search_contact.9.png b/res/drawable-xhdpi/bg_blk_search_contact.9.png
deleted file mode 100644
index fc3a05e..0000000
--- a/res/drawable-xhdpi/bg_blk_search_contact.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_contacts.png b/res/drawable-xhdpi/ic_launcher_contacts.png
deleted file mode 100644
index a2ba3bc..0000000
--- a/res/drawable-xhdpi/ic_launcher_contacts.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_phone.png b/res/drawable-xhdpi/ic_launcher_phone.png
deleted file mode 100644
index 2226b7b..0000000
--- a/res/drawable-xhdpi/ic_launcher_phone.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_shortcut_contact.png b/res/drawable-xhdpi/ic_launcher_shortcut_contact.png
deleted file mode 100644
index a5a71fa..0000000
--- a/res/drawable-xhdpi/ic_launcher_shortcut_contact.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_shortcut_directdial.png b/res/drawable-xhdpi/ic_launcher_shortcut_directdial.png
deleted file mode 100644
index b839084..0000000
--- a/res/drawable-xhdpi/ic_launcher_shortcut_directdial.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_launcher_shortcut_directmessage.png b/res/drawable-xhdpi/ic_launcher_shortcut_directmessage.png
deleted file mode 100644
index 35e1467..0000000
--- a/res/drawable-xhdpi/ic_launcher_shortcut_directmessage.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_add_contact_holo_light.png b/res/drawable-xhdpi/ic_menu_add_contact_holo_light.png
deleted file mode 100644
index f018afd..0000000
--- a/res/drawable-xhdpi/ic_menu_add_contact_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/ic_menu_contacts_holo_light.png b/res/drawable-xhdpi/ic_menu_contacts_holo_light.png
deleted file mode 100644
index c054505..0000000
--- a/res/drawable-xhdpi/ic_menu_contacts_holo_light.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/list_item_divider.9.png b/res/drawable-xhdpi/list_item_divider.9.png
deleted file mode 100644
index fd799ef..0000000
--- a/res/drawable-xhdpi/list_item_divider.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/list_pressed_holo.9.png b/res/drawable-xhdpi/list_pressed_holo.9.png
deleted file mode 100644
index d0fc7e0..0000000
--- a/res/drawable-xhdpi/list_pressed_holo.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/magnifying_glass.png b/res/drawable-xhdpi/magnifying_glass.png
deleted file mode 100644
index 9066772..0000000
--- a/res/drawable-xhdpi/magnifying_glass.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/tab_focused.9.png b/res/drawable-xhdpi/tab_focused.9.png
deleted file mode 100644
index b9aba3e..0000000
--- a/res/drawable-xhdpi/tab_focused.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/tab_left_arrow.png b/res/drawable-xhdpi/tab_left_arrow.png
deleted file mode 100644
index 9a2eef2..0000000
--- a/res/drawable-xhdpi/tab_left_arrow.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/tab_pressed.9.png b/res/drawable-xhdpi/tab_pressed.9.png
deleted file mode 100644
index 4a26f77..0000000
--- a/res/drawable-xhdpi/tab_pressed.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/tab_right_arrow.png b/res/drawable-xhdpi/tab_right_arrow.png
deleted file mode 100644
index 64a20ff..0000000
--- a/res/drawable-xhdpi/tab_right_arrow.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/tab_selected.9.png b/res/drawable-xhdpi/tab_selected.9.png
deleted file mode 100644
index 045a9c7..0000000
--- a/res/drawable-xhdpi/tab_selected.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/tab_unselected.9.png b/res/drawable-xhdpi/tab_unselected.9.png
deleted file mode 100644
index c11a765..0000000
--- a/res/drawable-xhdpi/tab_unselected.9.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable/btn_star_holo_dark.xml b/res/drawable/btn_star_holo_dark.xml
deleted file mode 100644
index 2949de9..0000000
--- a/res/drawable/btn_star_holo_dark.xml
+++ /dev/null
@@ -1,20 +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.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_checked="true" android:drawable="@drawable/btn_star_on_normal_holo_dark"/>
-    <item android:drawable="@drawable/btn_star_off_normal_holo_dark"/>
-</selector>
\ No newline at end of file
diff --git a/res/drawable/tab_indicator_bg.xml b/res/drawable/tab_indicator_bg.xml
deleted file mode 100644
index fb54954..0000000
--- a/res/drawable/tab_indicator_bg.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2008 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          http://www.apache.org/licenses/LICENSE-2.0
-  
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- Non focused states -->
-    <item 
-        android:state_focused="false" 
-        android:state_selected="false" 
-        android:state_pressed="false" 
-        android:drawable="@drawable/tab_unselected" />
-    <item 
-        android:state_focused="false" 
-        android:state_selected="true" 
-        android:state_pressed="false" 
-        android:drawable="@drawable/tab_selected" />
-
-    <!-- Focused states -->
-    <item 
-        android:state_focused="true" 
-        android:state_selected="true" 
-        android:state_pressed="false" 
-        android:drawable="@drawable/tab_focused" />
-
-    <!-- Pressed -->
-    <item 
-        android:state_pressed="true" 
-        android:drawable="@drawable/tab_pressed" />
-        
-</selector>
\ No newline at end of file
diff --git a/res/layout-sw580dp/contact_detail_container.xml b/res/layout-sw580dp/contact_detail_container.xml
index cdb789f..6ddc98d 100644
--- a/res/layout-sw580dp/contact_detail_container.xml
+++ b/res/layout-sw580dp/contact_detail_container.xml
@@ -17,7 +17,9 @@
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical">
+    android:paddingLeft="16dip"
+    android:paddingTop="16dip"
+    android:paddingRight="16dip">
 
     <android.support.v4.view.ViewPager
         android:id="@+id/pager"
diff --git a/res/layout-sw580dp/people_activity.xml b/res/layout-sw580dp/people_activity.xml
index 9cf695d..80ede2d 100644
--- a/res/layout-sw580dp/people_activity.xml
+++ b/res/layout-sw580dp/people_activity.xml
@@ -72,10 +72,7 @@
                 android:id="@+id/contact_detail_container"
                 layout="@layout/contact_detail_container"
                 android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:layout_marginTop="16dip"
-                android:layout_marginLeft="16dip"
-                android:layout_marginRight="16dip"/>
+                android:layout_height="match_parent" />
 
             <!-- This invisible worker fragment loads the contact's details -->
             <fragment
@@ -94,54 +91,41 @@
                 android:visibility="gone" />
         </view>
 
-        <view
-            class="com.android.contacts.widget.TransitionAnimationView"
+        <LinearLayout
             android:id="@+id/favorites_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            ex:clipMarginLeft="0dip"
-            ex:clipMarginTop="3dip"
-            ex:clipMarginRight="3dip"
-            ex:clipMarginBottom="9dip"
-            ex:enterAnimation="@android:animator/fade_in"
-            ex:exitAnimation="@android:animator/fade_out"
-            ex:animationDuration="200">
+            android:background="@drawable/list_background_holo"
+            android:baselineAligned="false">
 
-            <LinearLayout
-                android:layout_width="match_parent"
+            <!-- Starred -->
+            <FrameLayout
+                android:layout_width="0dip"
                 android:layout_height="match_parent"
-                android:background="@drawable/list_background_holo"
-                android:baselineAligned="false">
+                android:layout_weight="10"
+                android:background="@drawable/panel_favorites_holo_light">
 
-                <!-- Starred -->
-                <FrameLayout
-                    android:layout_width="0dip"
-                    android:layout_height="match_parent"
-                    android:layout_weight="10"
-                    android:background="@drawable/panel_favorites_holo_light">
-
-                    <fragment
-                        android:id="@+id/favorites_fragment"
-                        class="com.android.contacts.list.ContactTileListFragment"
-                        android:layout_height="match_parent"
-                        android:layout_width="match_parent"
-                        android:layout_marginRight="16dip"
-                        android:layout_marginLeft="16dip"/>
-
-                </FrameLayout>
-
-                <!-- Most Frequent -->
                 <fragment
-                    android:id="@+id/frequent_fragment"
-                    class="com.android.contacts.list.ContactTileFrequentFragment"
-                    android:layout_width="0dip"
+                    android:id="@+id/favorites_fragment"
+                    class="com.android.contacts.list.ContactTileListFragment"
                     android:layout_height="match_parent"
-                    android:layout_weight="8"
-                    android:layout_marginTop="16dip"
-                    android:layout_marginRight="16dip"/>
+                    android:layout_width="match_parent"
+                    android:layout_marginRight="16dip"
+                    android:layout_marginLeft="16dip"/>
 
-            </LinearLayout>
-        </view>
+            </FrameLayout>
+
+            <!-- Most Frequent -->
+            <fragment
+                android:id="@+id/frequent_fragment"
+                class="com.android.contacts.list.ContactTileFrequentFragment"
+                android:layout_width="0dip"
+                android:layout_height="match_parent"
+                android:layout_weight="8"
+                android:layout_marginTop="16dip"
+                android:layout_marginRight="16dip"/>
+
+        </LinearLayout>
 
     </LinearLayout>
 
diff --git a/res/layout-sw680dp-w1000dp/people_activity.xml b/res/layout-sw680dp-w1000dp/people_activity.xml
index e34ec21..f45324e 100644
--- a/res/layout-sw680dp-w1000dp/people_activity.xml
+++ b/res/layout-sw680dp-w1000dp/people_activity.xml
@@ -97,54 +97,41 @@
                 android:visibility="gone" />
         </view>
 
-        <view
-            class="com.android.contacts.widget.TransitionAnimationView"
+        <LinearLayout
             android:id="@+id/favorites_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            ex:clipMarginLeft="0dip"
-            ex:clipMarginTop="3dip"
-            ex:clipMarginRight="3dip"
-            ex:clipMarginBottom="9dip"
-            ex:enterAnimation="@android:animator/fade_in"
-            ex:exitAnimation="@android:animator/fade_out"
-            ex:animationDuration="200">
+            android:background="@drawable/list_background_holo"
+            android:baselineAligned="false">
 
-            <LinearLayout
-                android:layout_width="match_parent"
+            <!-- Starred -->
+            <FrameLayout
+                android:layout_width="0dip"
                 android:layout_height="match_parent"
-                android:background="@drawable/list_background_holo"
-                android:baselineAligned="false">
+                android:layout_weight="7"
+                android:background="@drawable/panel_favorites_holo_light">
 
-                <!-- Starred -->
-                <FrameLayout
-                    android:layout_width="0dip"
-                    android:layout_height="match_parent"
-                    android:layout_weight="7"
-                    android:background="@drawable/panel_favorites_holo_light">
-
-                    <fragment
-                        android:id="@+id/favorites_fragment"
-                        class="com.android.contacts.list.ContactTileListFragment"
-                        android:layout_height="match_parent"
-                        android:layout_width="match_parent"
-                        android:layout_marginRight="32dip"
-                        android:layout_marginLeft="32dip"/>
-
-                </FrameLayout>
-
-                <!-- Most Frequent -->
                 <fragment
-                    android:id="@+id/frequent_fragment"
-                    class="com.android.contacts.list.ContactTileFrequentFragment"
-                    android:layout_width="0dip"
+                    android:id="@+id/favorites_fragment"
+                    class="com.android.contacts.list.ContactTileListFragment"
                     android:layout_height="match_parent"
-                    android:layout_weight="3"
-                    android:layout_marginTop="32dip"
-                    android:layout_marginRight="16dip"/>
+                    android:layout_width="match_parent"
+                    android:layout_marginRight="32dip"
+                    android:layout_marginLeft="32dip"/>
 
-            </LinearLayout>
-        </view>
+            </FrameLayout>
+
+            <!-- Most Frequent -->
+            <fragment
+                android:id="@+id/frequent_fragment"
+                class="com.android.contacts.list.ContactTileFrequentFragment"
+                android:layout_width="0dip"
+                android:layout_height="match_parent"
+                android:layout_weight="3"
+                android:layout_marginTop="32dip"
+                android:layout_marginRight="16dip"/>
+
+        </LinearLayout>
 
     </com.android.contacts.widget.InterpolatingLayout>
 
diff --git a/res/layout-w470dp/contact_detail_container.xml b/res/layout-w470dp/contact_detail_container.xml
index a8ee0a5..fc401b7 100644
--- a/res/layout-w470dp/contact_detail_container.xml
+++ b/res/layout-w470dp/contact_detail_container.xml
@@ -16,8 +16,7 @@
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
+    android:layout_height="match_parent">
 
     <com.android.contacts.detail.ContactDetailFragmentCarousel
         android:id="@+id/fragment_carousel"
diff --git a/res/layout/call_log_incoming_call_icon.xml b/res/layout/call_log_incoming_call_icon.xml
deleted file mode 100644
index 8361655..0000000
--- a/res/layout/call_log_incoming_call_icon.xml
+++ /dev/null
@@ -1,24 +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.
--->
-
-<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/call_log_incoming_call_icon"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_marginRight="@dimen/call_log_icon_margin"
-    android:src="@drawable/ic_call_incoming_holo_dark"
-    android:contentDescription="@string/description_call_log_incoming_call"
-/>
diff --git a/res/layout/call_log_missed_call_icon.xml b/res/layout/call_log_missed_call_icon.xml
deleted file mode 100644
index 49177cd..0000000
--- a/res/layout/call_log_missed_call_icon.xml
+++ /dev/null
@@ -1,24 +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.
--->
-
-<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/call_log_missed_call_icon"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_marginRight="@dimen/call_log_icon_margin"
-    android:src="@drawable/ic_call_missed_holo_dark"
-    android:contentDescription="@string/description_call_log_missed_call"
-/>
diff --git a/res/layout/call_log_outgoing_call_icon.xml b/res/layout/call_log_outgoing_call_icon.xml
deleted file mode 100644
index f109eb4..0000000
--- a/res/layout/call_log_outgoing_call_icon.xml
+++ /dev/null
@@ -1,24 +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.
--->
-
-<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/call_log_outgoing_call_icon"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_marginRight="@dimen/call_log_icon_margin"
-    android:src="@drawable/ic_call_outgoing_holo_dark"
-    android:contentDescription="@string/description_call_log_outgoing_call"
-/>
diff --git a/res/layout/call_log_voicemail_icon.xml b/res/layout/call_log_voicemail_icon.xml
deleted file mode 100644
index 4fddcf0..0000000
--- a/res/layout/call_log_voicemail_icon.xml
+++ /dev/null
@@ -1,24 +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.
--->
-
-<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/call_log_voicemail_icon"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:layout_marginRight="@dimen/call_log_icon_margin"
-    android:src="@drawable/ic_call_voicemail_holo_dark"
-    android:contentDescription="@string/description_call_log_voicemail"
-/>
diff --git a/res/layout/contact_detail_container.xml b/res/layout/contact_detail_container.xml
index ff2eab1..a59ada4 100644
--- a/res/layout/contact_detail_container.xml
+++ b/res/layout/contact_detail_container.xml
@@ -25,8 +25,7 @@
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
+    android:layout_height="match_parent">
 
     <android.support.v4.view.ViewPager
         android:id="@+id/pager"
diff --git a/res/layout/contact_tile_frequent.xml b/res/layout/contact_tile_frequent.xml
index 5b51c78..992c643 100644
--- a/res/layout/contact_tile_frequent.xml
+++ b/res/layout/contact_tile_frequent.xml
@@ -24,19 +24,13 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" >
 
-        <view
-            android:id="@+id/image_container"
-            class="com.android.contacts.list.ContactTileImageContainer"
+        <com.android.contacts.widget.LayoutSuppressingQuickContactBadge
+            android:id="@+id/contact_tile_quick"
             android:layout_width="64dip"
             android:layout_height="64dip"
-            android:layout_alignParentRight="true">
-            <QuickContactBadge
-                android:id="@+id/contact_tile_quick"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:scaleType="centerCrop"
-                android:focusable="true" />
-        </view>
+            android:layout_alignParentRight="true"
+            android:scaleType="centerCrop"
+            android:focusable="true" />
 
         <LinearLayout
             android:layout_width="match_parent"
@@ -77,7 +71,7 @@
             android:layout_width="match_parent"
             android:layout_height="1px"
             android:background="?android:attr/listDivider"
-            android:layout_below="@id/image_container" />
+            android:layout_below="@id/contact_tile_quick" />
 
     </RelativeLayout>
 
diff --git a/res/layout/contact_tile_frequent_phone.xml b/res/layout/contact_tile_frequent_phone.xml
index 5c7b7e2..cae5ec2 100644
--- a/res/layout/contact_tile_frequent_phone.xml
+++ b/res/layout/contact_tile_frequent_phone.xml
@@ -27,20 +27,14 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" >
 
-        <view
-            android:id="@+id/image_container"
-            class="com.android.contacts.list.ContactTileImageContainer"
+        <com.android.contacts.widget.LayoutSuppressingQuickContactBadge
+            android:id="@id/contact_tile_quick"
             android:layout_width="64dip"
             android:layout_height="64dip"
-            android:layout_alignParentLeft="true">
-            <QuickContactBadge
-                android:id="@id/contact_tile_quick"
-                android:nextFocusRight="@id/contact_tile_frequent_phone"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:scaleType="centerCrop"
-                android:focusable="true" />
-        </view>
+            android:layout_alignParentLeft="true"
+            android:nextFocusRight="@id/contact_tile_frequent_phone"
+            android:scaleType="centerCrop"
+            android:focusable="true" />
 
         <TextView
             android:id="@+id/contact_tile_name"
@@ -49,7 +43,7 @@
             android:layout_marginLeft="8dip"
             android:textAppearance="?android:attr/textAppearanceMedium"
             android:layout_marginTop="8dip"
-            android:layout_toRightOf="@id/image_container"
+            android:layout_toRightOf="@id/contact_tile_quick"
             android:singleLine="true"
             android:fadingEdge="horizontal"
             android:fadingEdgeLength="3dip"
@@ -60,7 +54,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_below="@id/contact_tile_name"
-            android:layout_toRightOf="@id/image_container"
+            android:layout_toRightOf="@id/contact_tile_quick"
             android:gravity="center_vertical">
 
             <TextView
@@ -96,7 +90,7 @@
             android:layout_width="match_parent"
             android:layout_height="1px"
             android:background="?android:attr/listDivider"
-            android:layout_below="@id/image_container" />
+            android:layout_below="@id/contact_tile_quick" />
 
     </RelativeLayout>
 
diff --git a/res/layout/contact_tile_starred.xml b/res/layout/contact_tile_starred.xml
index 91438e9..cfc74d8 100644
--- a/res/layout/contact_tile_starred.xml
+++ b/res/layout/contact_tile_starred.xml
@@ -25,16 +25,11 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" >
 
-        <view
-            class="com.android.contacts.list.ContactTileImageContainer"
+        <com.android.contacts.widget.LayoutSuppressingImageView
+            android:id="@+id/contact_tile_image"
             android:layout_width="match_parent"
-            android:layout_height="match_parent">
-            <ImageView
-                 android:id="@+id/contact_tile_image"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:scaleType="centerCrop" />
-        </view>
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop" />
 
         <LinearLayout
             android:layout_width="match_parent"
diff --git a/res/layout/contact_tile_starred_quick_contact.xml b/res/layout/contact_tile_starred_quick_contact.xml
index 223f1b8..a396c41 100644
--- a/res/layout/contact_tile_starred_quick_contact.xml
+++ b/res/layout/contact_tile_starred_quick_contact.xml
@@ -24,16 +24,11 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" >
 
-        <view
-            class="com.android.contacts.list.ContactTileImageContainer"
+        <com.android.contacts.widget.LayoutSuppressingImageView
+            android:id="@+id/contact_tile_image"
             android:layout_width="match_parent"
-            android:layout_height="match_parent">
-            <ImageView
-                android:id="@+id/contact_tile_image"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:scaleType="centerCrop" />
-        </view>
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop" />
 
         <LinearLayout
             android:layout_width="match_parent"
diff --git a/res/layout/contact_tile_starred_secondary_target.xml b/res/layout/contact_tile_starred_secondary_target.xml
index 27ef3a3..ea15b86 100644
--- a/res/layout/contact_tile_starred_secondary_target.xml
+++ b/res/layout/contact_tile_starred_secondary_target.xml
@@ -24,16 +24,11 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" >
 
-        <view
-            class="com.android.contacts.list.ContactTileImageContainer"
+        <com.android.contacts.widget.LayoutSuppressingImageView
+            android:id="@+id/contact_tile_image"
             android:layout_width="match_parent"
-            android:layout_height="match_parent">
-            <ImageView
-                android:id="@+id/contact_tile_image"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:scaleType="centerCrop" />
-        </view>
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop" />
 
         <TextView
             android:id="@+id/contact_tile_name"
diff --git a/res/layout/dialtacts_activity.xml b/res/layout/dialtacts_activity.xml
index d1af632..7bcddc1 100644
--- a/res/layout/dialtacts_activity.xml
+++ b/res/layout/dialtacts_activity.xml
@@ -20,7 +20,7 @@
     android:layout_marginTop="?android:attr/actionBarSize"
     android:id="@+id/dialtacts_frame"
     >
-    <com.android.contacts.activities.DialtactsViewPager
+    <android.support.v4.view.ViewPager
         android:id="@+id/pager"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
diff --git a/res/layout/empty.xml b/res/layout/empty.xml
deleted file mode 100644
index 8e70c24..0000000
--- a/res/layout/empty.xml
+++ /dev/null
@@ -1,20 +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.
--->
-
-<View xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/empty"
-    android:layout_width="0dip"
-    android:layout_height="0dip" />
diff --git a/res/layout/search_bar.xml b/res/layout/search_bar.xml
deleted file mode 100644
index 7dfd8ec..0000000
--- a/res/layout/search_bar.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2010 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-  
-          http://www.apache.org/licenses/LICENSE-2.0
-  
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/search_bar"
-    android:layout_width="match_parent"
-    android:layout_height="64dip"
-    android:orientation="vertical"
-    android:focusable="true"
-    android:descendantFocusability="afterDescendants"
-    android:background="@drawable/bg_blk_search_contact">
-
-    <!-- Outer layout defines the entire search bar at the top of the screen -->
-    <LinearLayout
-        android:id="@+id/search_plate"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:paddingLeft="4dip"
-        android:paddingRight="10dip"
-        android:paddingTop="6dip"
-        android:paddingBottom="0dip"        
-        >
-
-        <!-- Inner layout contains the app icon, button(s) and EditText -->
-        <LinearLayout
-            android:id="@+id/search_edit_frame"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
-
-            <ImageView 
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content" 
-                android:src="@mipmap/ic_launcher_contacts"
-                android:layout_marginRight="7dip"
-                android:layout_gravity="center_vertical"
-                android:scaleType="centerInside" />
-              
-            <view
-                class="com.android.contacts.widget.SearchEditText"
-                android:id="@+id/search_src_text"
-                android:layout_height="wrap_content"
-                android:layout_width="0dip"
-                android:layout_weight="1.0"
-                android:layout_marginLeft="4dip"
-                android:layout_marginBottom="0dip"
-                android:singleLine="true"
-                android:ellipsize="end"
-                android:inputType="textNoSuggestions"
-                android:imeOptions="flagNoExtractUi"
-                android:hint="@string/search_bar_hint"
-                android:drawableRight="@drawable/magnifying_glass"
-                android:freezesText="true"
-            />
-        </LinearLayout>
-        
-    </LinearLayout>
-
-</LinearLayout>
diff --git a/res/layout/stream_item_container.xml b/res/layout/stream_item_container.xml
index de4f87d..903f6b0 100644
--- a/res/layout/stream_item_container.xml
+++ b/res/layout/stream_item_container.xml
@@ -21,23 +21,56 @@
     android:paddingLeft="@dimen/detail_update_section_side_padding"
     android:paddingRight="@dimen/detail_update_section_side_padding">
 
+    <!-- Clickable area -->
     <LinearLayout
         android:id="@+id/stream_item_content"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:paddingLeft="@dimen/detail_update_section_item_horizontal_padding"
-        android:paddingRight="@dimen/detail_update_section_item_horizontal_padding"
-        android:paddingTop="@dimen/detail_update_section_item_vertical_padding"
-        android:paddingBottom="@dimen/detail_update_section_item_vertical_padding"
         android:background="?android:attr/selectableItemBackground"
-        android:layout_gravity="center_vertical"
         android:orientation="vertical"
-        />
+        >
+
+        <!-- Images -->
+        <LinearLayout
+            android:id="@+id/stream_item_image_rows"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingLeft="@dimen/detail_update_section_item_horizontal_padding"
+            android:paddingRight="@dimen/detail_update_section_item_horizontal_padding"
+            android:paddingTop="@dimen/detail_update_section_item_vertical_padding"
+            android:paddingBottom="@dimen/detail_update_section_between_items_vertical_padding"
+            android:layout_gravity="center_vertical"
+            android:orientation="vertical"
+            >
+        </LinearLayout>
+
+        <!-- Text -->
+        <TextView android:id="@+id/stream_item_html"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textSize="16sp"
+            android:textColor="?android:attr/textColorPrimary" />
+        <TextView android:id="@+id/stream_item_attribution"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorSecondary"
+            android:ellipsize="end"
+            android:maxLines="1" />
+        <TextView android:id="@+id/stream_item_comments"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/detail_update_section_attribution_comments_padding"
+            android:textAppearance="?android:attr/textAppearanceSmall"
+            android:textColor="?android:attr/textColorSecondary"
+            android:maxLines="1"/>
+    </LinearLayout>
 
     <View
         android:id="@+id/horizontal_divider"
         android:layout_width="match_parent"
         android:layout_height="1px"
+        android:layout_marginTop="@dimen/detail_update_section_item_vertical_padding"
         android:background="?android:attr/dividerHorizontal"
         android:layout_gravity="bottom" />
 
diff --git a/res/layout/stream_item_photo.xml b/res/layout/stream_item_photo.xml
index 1fc3d0b..2649828 100644
--- a/res/layout/stream_item_photo.xml
+++ b/res/layout/stream_item_photo.xml
@@ -15,7 +15,7 @@
 -->
 
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android">
-    <ImageView
+    <com.android.contacts.widget.LayoutSuppressingImageView
         android:id="@+id/image"
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
diff --git a/res/layout/stream_item_row_two_images.xml b/res/layout/stream_item_row_images.xml
similarity index 97%
rename from res/layout/stream_item_row_two_images.xml
rename to res/layout/stream_item_row_images.xml
index 7858f6f..46e1f4f 100644
--- a/res/layout/stream_item_row_two_images.xml
+++ b/res/layout/stream_item_row_images.xml
@@ -37,6 +37,7 @@
     </view>
 
     <view
+        android:id="@+id/second_image_container"
         class="com.android.contacts.widget.ProportionalLayout"
         android:layout_width="0dip"
         android:layout_height="wrap_content"
diff --git a/res/layout/stream_item_row_one_image.xml b/res/layout/stream_item_row_one_image.xml
deleted file mode 100644
index 03dcedf..0000000
--- a/res/layout/stream_item_row_one_image.xml
+++ /dev/null
@@ -1,50 +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.
--->
-
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:ex="http://schemas.android.com/apk/res/com.android.contacts"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layout_marginTop="@dimen/detail_update_section_between_items_padding"
-    android:orientation="vertical">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="horizontal"
-        android:weightSum="2">
-
-        <view
-            class="com.android.contacts.widget.ProportionalLayout"
-            android:layout_width="0dip"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:layout_marginRight="@dimen/detail_update_section_between_items_padding"
-            ex:ratio="1"
-            ex:direction="widthToHeight">
-
-            <include
-                android:id="@+id/stream_item_first_image"
-                layout="@layout/stream_item_photo"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"/>
-
-        </view>
-
-    </LinearLayout>
-
-</LinearLayout>
diff --git a/res/layout/stream_item_row_text.xml b/res/layout/stream_item_row_text.xml
deleted file mode 100644
index 919ee52..0000000
--- a/res/layout/stream_item_row_text.xml
+++ /dev/null
@@ -1,49 +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.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <TextView android:id="@+id/stream_item_html"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:textSize="16sp"
-        android:textColor="?android:attr/textColorPrimary" />
-
-    <LinearLayout
-        android:orientation="horizontal"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-
-        <TextView android:id="@+id/stream_item_attribution"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:attr/textColorSecondary"
-            android:ellipsize="end"
-            android:maxLines="1" />
-        <TextView android:id="@+id/stream_item_comments"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginLeft="@dimen/detail_update_section_attribution_comments_padding"
-            android:textAppearance="?android:attr/textAppearanceSmall"
-            android:textColor="?android:attr/textColorSecondary"
-            android:maxLines="1"/>
-
-    </LinearLayout>
-</LinearLayout>
diff --git a/res/layout/tab_left_arrow.xml b/res/layout/tab_left_arrow.xml
deleted file mode 100644
index 0ed2e57..0000000
--- a/res/layout/tab_left_arrow.xml
+++ /dev/null
@@ -1,27 +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.
--->
-
-<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/arrow"
-    android:layout_width="32dip"
-    android:layout_height="37dip"
-    android:layout_alignParentLeft="true"
-    android:layout_alignParentTop="true"
-    android:layout_weight="1"
-    android:background="@drawable/tab_indicator_bg"
-    android:scaleType="centerInside"
-    android:src="@drawable/tab_left_arrow"
-    />
diff --git a/res/layout/tab_right_arrow.xml b/res/layout/tab_right_arrow.xml
deleted file mode 100644
index de69d8e..0000000
--- a/res/layout/tab_right_arrow.xml
+++ /dev/null
@@ -1,27 +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.
--->
-
-<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/arrow"
-    android:layout_width="32dip"
-    android:layout_height="37dip"
-    android:layout_alignParentRight="true"
-    android:layout_alignParentTop="true"
-    android:layout_weight="1"
-    android:background="@drawable/tab_indicator_bg"
-    android:scaleType="centerInside"
-    android:src="@drawable/tab_right_arrow"
-    />
diff --git a/res/menu/list.xml b/res/menu/list.xml
deleted file mode 100644
index f4a8751..0000000
--- a/res/menu/list.xml
+++ /dev/null
@@ -1,41 +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.
--->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:id="@+id/menu_search"
-        android:icon="@android:drawable/ic_menu_search"
-        android:title="@string/menu_search" />
-
-    <item
-        android:id="@+id/menu_add"
-        android:icon="@drawable/ic_menu_add_contact_holo_light"
-        android:title="@string/menu_newContact"
-        android:alphabeticShortcut="n" />
-
-    <item
-        android:id="@+id/menu_settings"
-        android:title="@string/menu_settings" />
-
-    <item
-        android:id="@+id/menu_accounts"
-        android:title="@string/menu_accounts" />
-
-    <item
-        android:id="@+id/menu_import_export"
-        android:title="@string/menu_import_export" />
-
-</menu>
diff --git a/res/values-land/dimens.xml b/res/values-land/dimens.xml
index 841eb75..350693a 100644
--- a/res/values-land/dimens.xml
+++ b/res/values-land/dimens.xml
@@ -16,6 +16,5 @@
 <resources>
     <dimen name="dialpad_digits_height">66dip</dimen>
     <dimen name="dialpad_digits_text_size">28sp</dimen>
-    <dimen name="dialpad_digits_margin_top">1dip</dimen>
     <dimen name="dialpad_digits_margin_bottom">50dip</dimen>
 </resources>
diff --git a/res/values-sw580dp/dimens.xml b/res/values-sw580dp/dimens.xml
index 264565f..d074324 100644
--- a/res/values-sw580dp/dimens.xml
+++ b/res/values-sw580dp/dimens.xml
@@ -33,7 +33,6 @@
     <!-- Center vertically -->
     <dimen name="quick_contact_top_position">-1px</dimen>
     <!-- Contact list (vertical scroll bar comes left) -->
-    <dimen name="directory_header_height">24dip</dimen>
     <dimen name="contact_browser_list_top_margin">16dip</dimen>
     <dimen name="contact_browser_list_header_left_margin">@dimen/list_visible_scrollbar_padding</dimen>
     <dimen name="contact_browser_list_header_right_margin">24dip</dimen>
@@ -47,12 +46,6 @@
     <dimen name="join_header_top_margin">16dip</dimen>
     <dimen name="join_header_bottom_margin">16dip</dimen>
 
-    <!-- Margins and padding for text in widget -->
-    <dimen name="widget_snippet_top_margin">5dip</dimen>
-    <dimen name="widget_snippet_bottom_margin">6dip</dimen>
-    <dimen name="widget_snippet_top_padding">6dip</dimen>
-    <dimen name="widget_snippet_bottom_padding">3dip</dimen>
-
     <!-- Left padding for a group member list item -->
     <dimen name="group_member_item_left_padding">12dip</dimen>
     <!-- Left margin for the group member list to match the built in margin in the autocomplete asset -->
diff --git a/res/values-sw680dp-w1000dp/dimens.xml b/res/values-sw680dp-w1000dp/dimens.xml
index e492e5b..680bcee 100644
--- a/res/values-sw680dp-w1000dp/dimens.xml
+++ b/res/values-sw680dp-w1000dp/dimens.xml
@@ -15,7 +15,6 @@
 -->
 <resources>
     <dimen name="group_detail_border_padding">32dip</dimen>
-    <dimen name="group_detail_side_margin">32dip</dimen>
     <dimen name="group_editor_side_padding">64dip</dimen>
     <dimen name="detail_contact_photo_margin">16dip</dimen>
     <dimen name="contact_detail_list_top_padding">32dip</dimen>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index bc70cbf..48e63e5 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -49,9 +49,6 @@
     <!-- Colour of voicemail progress bar to the left of position indicator. -->
     <color name="voicemail_playback_seek_bar_already_played">#ccffffff</color>
 
-    <!-- Colour of text that appears on the voicemail ui. -->
-    <color name="voicemail_playback_ui_text">#cc696969</color>
-
     <!-- Color of the theme of the People app -->
     <color name="people_app_theme_color">#33B5E5</color>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index db81fed..b6620f5 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -149,9 +149,6 @@
     <!-- Border padding for the group detail fragment -->
     <dimen name="group_detail_border_padding">0dip</dimen>
 
-    <!-- Left and right margin for the divider in the group detail fragment -->
-    <dimen name="group_detail_side_margin">0dip</dimen>
-
     <!-- Height of the quick contact photo container (for screens that are too large to use the screen width/height as a constraint)-->
     <dimen name="quick_contact_photo_container_height">200dip</dimen>
 
@@ -161,9 +158,7 @@
     <dimen name="dialpad_digits_text_size">35sp</dimen>
 
     <!-- Just used in landscape mode -->
-    <dimen name="dialpad_button_height">0px</dimen>
     <dimen name="dialpad_digits_height">0px</dimen>
-    <dimen name="dialpad_digits_margin_top">0px</dimen>
     <dimen name="dialpad_digits_margin_bottom">0px</dimen>
 
     <!-- Width of search view in action bar.  Use 0dip for MATCH_PARENT -->
@@ -198,7 +193,6 @@
     <!-- Call Log -->
     <dimen name="call_log_call_action_size">32dip</dimen>
     <dimen name="call_log_call_action_width">48dip</dimen>
-    <dimen name="call_log_call_action_height">64dip</dimen>
     <dimen name="call_log_icon_margin">4dip</dimen>
     <dimen name="call_log_inner_margin">8dip</dimen>
     <dimen name="call_log_outer_margin">16dip</dimen>
@@ -219,9 +213,6 @@
     <dimen name="contact_filter_icon_size">32dip</dimen>
     <dimen name="contact_filter_header_min_height">24dip</dimen>
 
-    <!-- Height for directory headers in contact lists -->
-    <dimen name="directory_header_height">24dip</dimen>
-
     <!--  Vertical and horizontal padding in between contact tiles -->
     <dimen name="contact_tile_divider_padding">1dip</dimen>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a88e6a8..936f844 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1280,9 +1280,6 @@
     <!-- An allowable value for the "view names as" contact display option  -->
     <string name="display_options_view_family_name_first">Family name first</string>
 
-    <!-- Gray hint displayed in the search field in Contacts when empty -->
-    <string name="search_bar_hint">Search contacts</string>
-
     <!-- An option in the 'Contact photo' dialog, if there is no photo yet [CHAR LIMIT=50] -->
     <string name="take_photo">Take photo</string>
 
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index b7cc87d..e85f5f5 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -28,6 +28,7 @@
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
+import android.content.AsyncTaskLoader;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
@@ -35,14 +36,12 @@
 import android.content.Entity;
 import android.content.Entity.NamedContentValues;
 import android.content.Intent;
-import android.content.Loader;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
 import android.provider.ContactsContract.CommonDataKinds.Photo;
@@ -56,14 +55,13 @@
 import android.provider.ContactsContract.StreamItems;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.LongSparseArray;
 
 import java.io.ByteArrayOutputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -71,9 +69,11 @@
 /**
  * Loads a single Contact and all it constituent RawContacts.
  */
-public class ContactLoader extends Loader<ContactLoader.Result> {
+public class ContactLoader extends AsyncTaskLoader<ContactLoader.Result> {
     private static final String TAG = "ContactLoader";
 
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
     private final Uri mRequestedUri;
     private Uri mLookupUri;
     private boolean mLoadGroupMetaData;
@@ -81,13 +81,26 @@
     private final boolean mLoadInvitableAccountTypes;
     private Result mContact;
     private ForceLoadContentObserver mObserver;
-    private boolean mDestroyed;
     private final Set<Long> mNotifiedRawContactIds = Sets.newHashSet();
 
     public interface Listener {
         public void onContactLoaded(Result contact);
     }
 
+    public ContactLoader(Context context, Uri lookupUri) {
+        this(context, lookupUri, false, false, false);
+    }
+
+    public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
+            boolean loadStreamItems, boolean loadInvitableAccountTypes) {
+        super(context);
+        mLookupUri = lookupUri;
+        mRequestedUri = lookupUri;
+        mLoadGroupMetaData = loadGroupMetaData;
+        mLoadStreamItems = loadStreamItems;
+        mLoadInvitableAccountTypes = loadInvitableAccountTypes;
+    }
+
     /**
      * The result of a load operation. Contains all data necessary to display the contact.
      */
@@ -118,7 +131,7 @@
         private final Integer mPresence;
         private final ArrayList<Entity> mEntities;
         private final ArrayList<StreamItemEntry> mStreamItems;
-        private final HashMap<Long, DataStatus> mStatuses;
+        private final LongSparseArray<DataStatus> mStatuses;
         private final ArrayList<AccountType> mInvitableAccountTypes;
 
         private String mDirectoryDisplayName;
@@ -129,7 +142,6 @@
 
         private ArrayList<GroupMetaData> mGroups;
 
-        private boolean mLoadingPhoto;
         private byte[] mPhotoBinaryData;
         private final boolean mSendToVoicemail;
         private final String mCustomRingtone;
@@ -197,7 +209,7 @@
             mId = id;
             mEntities = new ArrayList<Entity>();
             mStreamItems = new ArrayList<StreamItemEntry>();
-            mStatuses = new HashMap<Long, DataStatus>();
+            mStatuses = new LongSparseArray<DataStatus>();
             mNameRawContactId = nameRawContactId;
             mDisplayNameSource = displayNameSource;
             mPhotoId = photoId;
@@ -244,7 +256,6 @@
 
             mGroups = from.mGroups;
 
-            mLoadingPhoto = from.mLoadingPhoto;
             mPhotoBinaryData = from.mPhotoBinaryData;
             mSendToVoicemail = from.mSendToVoicemail;
             mCustomRingtone = from.mCustomRingtone;
@@ -263,10 +274,6 @@
             mDirectoryExportSupport = exportSupport;
         }
 
-        private void setLoadingPhoto(boolean flag) {
-            mLoadingPhoto = flag;
-        }
-
         private void setPhotoBinaryData(byte[] photoBinaryData) {
             mPhotoBinaryData = photoBinaryData;
         }
@@ -390,7 +397,7 @@
             return mStreamItems;
         }
 
-        public HashMap<Long, DataStatus> getStatuses() {
+        public LongSparseArray<DataStatus> getStatuses() {
             return mStatuses;
         }
 
@@ -423,10 +430,6 @@
             return mDirectoryAccountName;
         }
 
-        public boolean isLoadingPhoto() {
-            return mLoadingPhoto;
-        }
-
         public byte[] getPhotoBinaryData() {
             return mPhotoBinaryData;
         }
@@ -679,491 +682,495 @@
         public final static int FAVORITES = 7;
     }
 
-    private final class LoadContactTask extends AsyncTask<Void, Void, Result> {
-
-        @Override
-        protected Result doInBackground(Void... args) {
-            try {
-                final ContentResolver resolver = getContext().getContentResolver();
-                final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(
-                        resolver, mLookupUri);
-                Result result = loadContactEntity(resolver, uriCurrentFormat);
-                if (!result.isNotFound()) {
-                    if (result.isDirectoryEntry()) {
-                        loadDirectoryMetaData(result);
-                    } else if (mLoadGroupMetaData) {
-                        loadGroupMetaData(result);
-                    }
-                    if (mLoadStreamItems) {
-                        loadStreamItems(result);
-                    }
-                    loadPhotoBinaryData(result);
-
-                    // Note ME profile should never have "Add connection"
-                    if (mLoadInvitableAccountTypes && !result.isUserProfile()) {
-                        loadInvitableAccountTypes(result);
-                    }
+    @Override
+    public Result loadInBackground() {
+        try {
+            final ContentResolver resolver = getContext().getContentResolver();
+            final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(
+                    resolver, mLookupUri);
+            Result result = loadContactEntity(resolver, uriCurrentFormat);
+            if (!result.isNotFound()) {
+                if (result.isDirectoryEntry()) {
+                    loadDirectoryMetaData(result);
+                } else if (mLoadGroupMetaData) {
+                    loadGroupMetaData(result);
                 }
-                return result;
-            } catch (Exception e) {
-                Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
-                return Result.forError(mRequestedUri, e);
+                if (mLoadStreamItems) {
+                    loadStreamItems(result);
+                }
+                loadPhotoBinaryData(result);
+
+                // Note ME profile should never have "Add connection"
+                if (mLoadInvitableAccountTypes && !result.isUserProfile()) {
+                    loadInvitableAccountTypes(result);
+                }
             }
+            return result;
+        } catch (Exception e) {
+            Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
+            return Result.forError(mRequestedUri, e);
+        }
+    }
+
+    private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
+        Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
+        Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
+                Contacts.Entity.RAW_CONTACT_ID);
+        if (cursor == null) {
+            Log.e(TAG, "No cursor returned in loadContactEntity");
+            return Result.forNotFound(mRequestedUri);
         }
 
-        private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
-            Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
-            Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
-                    Contacts.Entity.RAW_CONTACT_ID);
-            if (cursor == null) {
-                Log.e(TAG, "No cursor returned in loadContactEntity");
+        try {
+            if (!cursor.moveToFirst()) {
+                cursor.close();
                 return Result.forNotFound(mRequestedUri);
             }
 
-            try {
-                if (!cursor.moveToFirst()) {
-                    cursor.close();
-                    return Result.forNotFound(mRequestedUri);
+            long currentRawContactId = -1;
+            Entity entity = null;
+            Result result = loadContactHeaderData(cursor, contactUri);
+            ArrayList<Entity> entities = result.getEntities();
+            LongSparseArray<DataStatus> statuses = result.getStatuses();
+            for (; !cursor.isAfterLast(); cursor.moveToNext()) {
+                long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
+                if (rawContactId != currentRawContactId) {
+                    currentRawContactId = rawContactId;
+                    entity = new android.content.Entity(loadRawContact(cursor));
+                    entities.add(entity);
                 }
+                if (!cursor.isNull(ContactQuery.DATA_ID)) {
+                    ContentValues data = loadData(cursor);
+                    entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
 
-                long currentRawContactId = -1;
-                Entity entity = null;
-                Result result = loadContactHeaderData(cursor, contactUri);
-                ArrayList<Entity> entities = result.getEntities();
-                HashMap<Long, DataStatus> statuses = result.getStatuses();
-                for (; !cursor.isAfterLast(); cursor.moveToNext()) {
-                    long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
-                    if (rawContactId != currentRawContactId) {
-                        currentRawContactId = rawContactId;
-                        entity = new android.content.Entity(loadRawContact(cursor));
-                        entities.add(entity);
-                    }
-                    if (!cursor.isNull(ContactQuery.DATA_ID)) {
-                        ContentValues data = loadData(cursor);
-                        entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
-
-                        if (!cursor.isNull(ContactQuery.PRESENCE)
-                                || !cursor.isNull(ContactQuery.STATUS)) {
-                            final DataStatus status = new DataStatus(cursor);
-                            final long dataId = cursor.getLong(ContactQuery.DATA_ID);
-                            statuses.put(dataId, status);
-                        }
+                    if (!cursor.isNull(ContactQuery.PRESENCE)
+                            || !cursor.isNull(ContactQuery.STATUS)) {
+                        final DataStatus status = new DataStatus(cursor);
+                        final long dataId = cursor.getLong(ContactQuery.DATA_ID);
+                        statuses.put(dataId, status);
                     }
                 }
-
-                return result;
-            } finally {
-                cursor.close();
             }
+
+            return result;
+        } finally {
+            cursor.close();
         }
+    }
 
-        /**
-         * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
-         * not found, returns null
-         */
-        private void loadPhotoBinaryData(Result contactData) {
+    /**
+     * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
+     * not found, returns null
+     */
+    private void loadPhotoBinaryData(Result contactData) {
 
-            // If we have a photo URI, try loading that first.
-            String photoUri = contactData.getPhotoUri();
-            if (photoUri != null) {
+        // If we have a photo URI, try loading that first.
+        String photoUri = contactData.getPhotoUri();
+        if (photoUri != null) {
+            try {
+                AssetFileDescriptor fd = getContext().getContentResolver()
+                       .openAssetFileDescriptor(Uri.parse(photoUri), "r");
+                byte[] buffer = new byte[16 * 1024];
+                FileInputStream fis = fd.createInputStream();
+                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                 try {
-                    AssetFileDescriptor fd = getContext().getContentResolver()
-                           .openAssetFileDescriptor(Uri.parse(photoUri), "r");
-                    byte[] buffer = new byte[16 * 1024];
-                    FileInputStream fis = fd.createInputStream();
-                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    int size;
+                    while ((size = fis.read(buffer)) != -1) {
+                        baos.write(buffer, 0, size);
+                    }
+                    contactData.setPhotoBinaryData(baos.toByteArray());
+                } finally {
+                    fis.close();
+                    fd.close();
+                }
+                return;
+            } catch (IOException ioe) {
+                // Just fall back to the case below.
+            }
+        }
+
+        // If we couldn't load from a file, fall back to the data blob.
+        final long photoId = contactData.getPhotoId();
+        if (photoId <= 0) {
+            // No photo ID
+            return;
+        }
+
+        for (Entity entity : contactData.getEntities()) {
+            for (NamedContentValues subValue : entity.getSubValues()) {
+                final ContentValues entryValues = subValue.values;
+                final long dataId = entryValues.getAsLong(Data._ID);
+                if (dataId == photoId) {
+                    final String mimeType = entryValues.getAsString(Data.MIMETYPE);
+                    // Correct Data Id but incorrect MimeType? Don't load
+                    if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                        return;
+                    }
+                    contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
+     */
+    private void loadInvitableAccountTypes(Result contactData) {
+        Map<AccountTypeWithDataSet, AccountType> invitables =
+                AccountTypeManager.getInstance(getContext()).getUsableInvitableAccountTypes();
+        if (invitables.isEmpty()) {
+            return;
+        }
+
+        Map<AccountTypeWithDataSet, AccountType> result = Maps.newHashMap(invitables);
+
+        // Remove the ones that already have a raw contact in the current contact
+        for (Entity entity : contactData.getEntities()) {
+            final ContentValues values = entity.getEntityValues();
+            final AccountTypeWithDataSet type = AccountTypeWithDataSet.get(
+                    values.getAsString(RawContacts.ACCOUNT_TYPE),
+                    values.getAsString(RawContacts.DATA_SET));
+            result.remove(type);
+        }
+
+        // Set to mInvitableAccountTypes
+        contactData.mInvitableAccountTypes.addAll(result.values());
+    }
+
+    /**
+     * Extracts Contact level columns from the cursor.
+     */
+    private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
+        final String directoryParameter =
+                contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
+        final long directoryId = directoryParameter == null
+                ? Directory.DEFAULT
+                : Long.parseLong(directoryParameter);
+        final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
+        final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
+        final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
+        final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
+        final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
+        final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
+        final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
+        final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
+        final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
+        final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
+        final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
+                ? null
+                : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
+        final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
+        final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
+        final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
+
+        Uri lookupUri;
+        if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
+            lookupUri = ContentUris.withAppendedId(
+                Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
+        } else {
+            lookupUri = contactUri;
+        }
+
+        return new Result(mRequestedUri, contactUri, lookupUri, directoryId, lookupKey,
+                contactId, nameRawContactId, displayNameSource, photoId, photoUri, displayName,
+                altDisplayName, phoneticName, starred, presence, sendToVoicemail,
+                customRingtone, isUserProfile);
+    }
+
+    /**
+     * Extracts RawContact level columns from the cursor.
+     */
+    private ContentValues loadRawContact(Cursor cursor) {
+        ContentValues cv = new ContentValues();
+
+        cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
+
+        cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
+
+        return cv;
+    }
+
+    /**
+     * Extracts Data level columns from the cursor.
+     */
+    private ContentValues loadData(Cursor cursor) {
+        ContentValues cv = new ContentValues();
+
+        cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
+
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
+
+        return cv;
+    }
+
+    private void cursorColumnToContentValues(
+            Cursor cursor, ContentValues values, int index) {
+        switch (cursor.getType(index)) {
+            case Cursor.FIELD_TYPE_NULL:
+                // don't put anything in the content values
+                break;
+            case Cursor.FIELD_TYPE_INTEGER:
+                values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
+                break;
+            case Cursor.FIELD_TYPE_STRING:
+                values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
+                break;
+            case Cursor.FIELD_TYPE_BLOB:
+                values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
+                break;
+            default:
+                throw new IllegalStateException("Invalid or unhandled data type");
+        }
+    }
+
+    private void loadDirectoryMetaData(Result result) {
+        long directoryId = result.getDirectoryId();
+
+        Cursor cursor = getContext().getContentResolver().query(
+                ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
+                DirectoryQuery.COLUMNS, null, null, null);
+        if (cursor == null) {
+            return;
+        }
+        try {
+            if (cursor.moveToFirst()) {
+                final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
+                final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
+                final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
+                final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
+                final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
+                final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
+                String directoryType = null;
+                if (!TextUtils.isEmpty(packageName)) {
+                    PackageManager pm = getContext().getPackageManager();
                     try {
-                        int size;
-                        while ((size = fis.read(buffer)) != -1) {
-                            baos.write(buffer, 0, size);
-                        }
-                        contactData.setPhotoBinaryData(baos.toByteArray());
-                    } finally {
-                        fis.close();
-                        fd.close();
-                    }
-                    return;
-                } catch (IOException ioe) {
-                    // Just fall back to the case below.
-                }
-            }
-
-            // If we couldn't load from a file, fall back to the data blob.
-            final long photoId = contactData.getPhotoId();
-            if (photoId <= 0) {
-                // No photo ID
-                return;
-            }
-
-            for (Entity entity : contactData.getEntities()) {
-                for (NamedContentValues subValue : entity.getSubValues()) {
-                    final ContentValues entryValues = subValue.values;
-                    final long dataId = entryValues.getAsLong(Data._ID);
-                    if (dataId == photoId) {
-                        final String mimeType = entryValues.getAsString(Data.MIMETYPE);
-                        // Correct Data Id but incorrect MimeType? Don't load
-                        if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                            return;
-                        }
-                        contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
-                        break;
+                        Resources resources = pm.getResourcesForApplication(packageName);
+                        directoryType = resources.getString(typeResourceId);
+                    } catch (NameNotFoundException e) {
+                        Log.w(TAG, "Contact directory resource not found: "
+                                + packageName + "." + typeResourceId);
                     }
                 }
+
+                result.setDirectoryMetaData(
+                        displayName, directoryType, accountType, accountName, exportSupport);
             }
+        } finally {
+            cursor.close();
         }
+    }
 
-        /**
-         * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
-         */
-        private void loadInvitableAccountTypes(Result contactData) {
-            Map<AccountTypeWithDataSet, AccountType> invitables =
-                    AccountTypeManager.getInstance(getContext()).getUsableInvitableAccountTypes();
-            if (invitables.isEmpty()) {
-                return;
-            }
-
-            HashMap<AccountTypeWithDataSet, AccountType> result = Maps.newHashMap(invitables);
-
-            // Remove the ones that already have a raw contact in the current contact
-            for (Entity entity : contactData.getEntities()) {
-                final ContentValues values = entity.getEntityValues();
-                final AccountTypeWithDataSet type = AccountTypeWithDataSet.get(
-                        values.getAsString(RawContacts.ACCOUNT_TYPE),
-                        values.getAsString(RawContacts.DATA_SET));
-                result.remove(type);
-            }
-
-            // Set to mInvitableAccountTypes
-            contactData.mInvitableAccountTypes.addAll(result.values());
-        }
-
-        /**
-         * Extracts Contact level columns from the cursor.
-         */
-        private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
-            final String directoryParameter =
-                    contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
-            final long directoryId = directoryParameter == null
-                    ? Directory.DEFAULT
-                    : Long.parseLong(directoryParameter);
-            final long contactId = cursor.getLong(ContactQuery.CONTACT_ID);
-            final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY);
-            final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
-            final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
-            final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
-            final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
-            final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
-            final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
-            final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
-            final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0;
-            final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE)
-                    ? null
-                    : cursor.getInt(ContactQuery.CONTACT_PRESENCE);
-            final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1;
-            final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE);
-            final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1;
-
-            Uri lookupUri;
-            if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) {
-                lookupUri = ContentUris.withAppendedId(
-                    Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId);
-            } else {
-                lookupUri = contactUri;
-            }
-
-            return new Result(mRequestedUri, contactUri, lookupUri, directoryId, lookupKey,
-                    contactId, nameRawContactId, displayNameSource, photoId, photoUri, displayName,
-                    altDisplayName, phoneticName, starred, presence, sendToVoicemail,
-                    customRingtone, isUserProfile);
-        }
-
-        /**
-         * Extracts RawContact level columns from the cursor.
-         */
-        private ContentValues loadRawContact(Cursor cursor) {
-            ContentValues cv = new ContentValues();
-
-            cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
-
-            cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED);
-
-            return cv;
-        }
-
-        /**
-         * Extracts Data level columns from the cursor.
-         */
-        private ContentValues loadData(Cursor cursor) {
-            ContentValues cv = new ContentValues();
-
-            cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
-
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID);
-            cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
-
-            return cv;
-        }
-
-        private void cursorColumnToContentValues(
-                Cursor cursor, ContentValues values, int index) {
-            switch (cursor.getType(index)) {
-                case Cursor.FIELD_TYPE_NULL:
-                    // don't put anything in the content values
-                    break;
-                case Cursor.FIELD_TYPE_INTEGER:
-                    values.put(ContactQuery.COLUMNS[index], cursor.getLong(index));
-                    break;
-                case Cursor.FIELD_TYPE_STRING:
-                    values.put(ContactQuery.COLUMNS[index], cursor.getString(index));
-                    break;
-                case Cursor.FIELD_TYPE_BLOB:
-                    values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index));
-                    break;
-                default:
-                    throw new IllegalStateException("Invalid or unhandled data type");
-            }
-        }
-
-        private void loadDirectoryMetaData(Result result) {
-            long directoryId = result.getDirectoryId();
-
-            Cursor cursor = getContext().getContentResolver().query(
-                    ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId),
-                    DirectoryQuery.COLUMNS, null, null, null);
-            if (cursor == null) {
-                return;
-            }
-            try {
-                if (cursor.moveToFirst()) {
-                    final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME);
-                    final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME);
-                    final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID);
-                    final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE);
-                    final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME);
-                    final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT);
-                    String directoryType = null;
-                    if (!TextUtils.isEmpty(packageName)) {
-                        PackageManager pm = getContext().getPackageManager();
-                        try {
-                            Resources resources = pm.getResourcesForApplication(packageName);
-                            directoryType = resources.getString(typeResourceId);
-                        } catch (NameNotFoundException e) {
-                            Log.w(TAG, "Contact directory resource not found: "
-                                    + packageName + "." + typeResourceId);
-                        }
-                    }
-
-                    result.setDirectoryMetaData(
-                            displayName, directoryType, accountType, accountName, exportSupport);
+    /**
+     * Loads groups meta-data for all groups associated with all constituent raw contacts'
+     * accounts.
+     */
+    private void loadGroupMetaData(Result result) {
+        StringBuilder selection = new StringBuilder();
+        ArrayList<String> selectionArgs = new ArrayList<String>();
+        for (Entity entity : result.mEntities) {
+            ContentValues values = entity.getEntityValues();
+            String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
+            String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
+            String dataSet = values.getAsString(RawContacts.DATA_SET);
+            if (accountName != null && accountType != null) {
+                if (selection.length() != 0) {
+                    selection.append(" OR ");
                 }
-            } finally {
-                cursor.close();
-            }
-        }
+                selection.append(
+                        "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
+                selectionArgs.add(accountName);
+                selectionArgs.add(accountType);
 
-        /**
-         * Loads groups meta-data for all groups associated with all constituent raw contacts'
-         * accounts.
-         */
-        private void loadGroupMetaData(Result result) {
-            StringBuilder selection = new StringBuilder();
-            ArrayList<String> selectionArgs = new ArrayList<String>();
-            for (Entity entity : result.mEntities) {
-                ContentValues values = entity.getEntityValues();
-                String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
-                String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
-                String dataSet = values.getAsString(RawContacts.DATA_SET);
-                if (accountName != null && accountType != null) {
-                    if (selection.length() != 0) {
-                        selection.append(" OR ");
-                    }
-                    selection.append(
-                            "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?");
-                    selectionArgs.add(accountName);
-                    selectionArgs.add(accountType);
-
-                    if (dataSet != null) {
-                        selection.append(" AND " + Groups.DATA_SET + "=?");
-                        selectionArgs.add(dataSet);
-                    } else {
-                        selection.append(" AND " + Groups.DATA_SET + " IS NULL");
-                    }
-                    selection.append(")");
-                }
-            }
-            Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
-                    GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
-                    null);
-            try {
-                while (cursor.moveToNext()) {
-                    final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
-                    final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
-                    final String dataSet = cursor.getString(GroupQuery.DATA_SET);
-                    final long groupId = cursor.getLong(GroupQuery.ID);
-                    final String title = cursor.getString(GroupQuery.TITLE);
-                    final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
-                            ? false
-                            : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
-                    final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
-                            ? false
-                            : cursor.getInt(GroupQuery.FAVORITES) != 0;
-
-                    result.addGroupMetaData(new GroupMetaData(
-                            accountName, accountType, dataSet, groupId, title, defaultGroup,
-                            favorites));
-                }
-            } finally {
-                cursor.close();
-            }
-        }
-
-        /**
-         * Loads all stream items and stream item photos belonging to this contact.
-         */
-        private void loadStreamItems(Result result) {
-            Cursor cursor = getContext().getContentResolver().query(
-                    Contacts.CONTENT_LOOKUP_URI.buildUpon()
-                            .appendPath(result.getLookupKey())
-                            .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
-                    null, null, null, null);
-            Map<Long, StreamItemEntry> streamItemsById = new HashMap<Long, StreamItemEntry>();
-            ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
-            try {
-                while (cursor.moveToNext()) {
-                    StreamItemEntry streamItem = new StreamItemEntry(cursor);
-                    streamItemsById.put(streamItem.getId(), streamItem);
-                    streamItems.add(streamItem);
-                }
-            } finally {
-                cursor.close();
-            }
-
-            // Now retrieve any photo records associated with the stream items.
-            if (!streamItems.isEmpty()) {
-                if (result.isUserProfile()) {
-                    // If the stream items we're loading are for the profile, we can't bulk-load the
-                    // stream items with a custom selection.
-                    for (StreamItemEntry entry : streamItems) {
-                        Cursor siCursor = getContext().getContentResolver().query(
-                                Uri.withAppendedPath(
-                                        ContentUris.withAppendedId(
-                                                StreamItems.CONTENT_URI, entry.getId()),
-                                        StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
-                                null, null, null, null);
-                        try {
-                            while (siCursor.moveToNext()) {
-                                entry.addPhoto(new StreamItemPhotoEntry(siCursor));
-                            }
-                        } finally {
-                            siCursor.close();
-                        }
-                    }
+                if (dataSet != null) {
+                    selection.append(" AND " + Groups.DATA_SET + "=?");
+                    selectionArgs.add(dataSet);
                 } else {
-                    String[] streamItemIdArr = new String[streamItems.size()];
-                    StringBuilder streamItemPhotoSelection = new StringBuilder();
-                    streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
-                    for (int i = 0; i < streamItems.size(); i++) {
-                        if (i > 0) {
-                            streamItemPhotoSelection.append(",");
-                        }
-                        streamItemPhotoSelection.append("?");
-                        streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
-                    }
-                    streamItemPhotoSelection.append(")");
-                    Cursor sipCursor = getContext().getContentResolver().query(
-                            StreamItems.CONTENT_PHOTO_URI,
-                            null, streamItemPhotoSelection.toString(), streamItemIdArr,
-                            StreamItemPhotos.STREAM_ITEM_ID);
+                    selection.append(" AND " + Groups.DATA_SET + " IS NULL");
+                }
+                selection.append(")");
+            }
+        }
+        Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
+                GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
+                null);
+        try {
+            while (cursor.moveToNext()) {
+                final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME);
+                final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE);
+                final String dataSet = cursor.getString(GroupQuery.DATA_SET);
+                final long groupId = cursor.getLong(GroupQuery.ID);
+                final String title = cursor.getString(GroupQuery.TITLE);
+                final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD)
+                        ? false
+                        : cursor.getInt(GroupQuery.AUTO_ADD) != 0;
+                final boolean favorites = cursor.isNull(GroupQuery.FAVORITES)
+                        ? false
+                        : cursor.getInt(GroupQuery.FAVORITES) != 0;
+
+                result.addGroupMetaData(new GroupMetaData(
+                        accountName, accountType, dataSet, groupId, title, defaultGroup,
+                        favorites));
+            }
+        } finally {
+            cursor.close();
+        }
+    }
+
+    /**
+     * Loads all stream items and stream item photos belonging to this contact.
+     */
+    private void loadStreamItems(Result result) {
+        Cursor cursor = getContext().getContentResolver().query(
+                Contacts.CONTENT_LOOKUP_URI.buildUpon()
+                        .appendPath(result.getLookupKey())
+                        .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
+                null, null, null, null);
+        LongSparseArray<StreamItemEntry> streamItemsById =
+                new LongSparseArray<StreamItemEntry>();
+        ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
+        try {
+            while (cursor.moveToNext()) {
+                StreamItemEntry streamItem = new StreamItemEntry(cursor);
+                streamItemsById.put(streamItem.getId(), streamItem);
+                streamItems.add(streamItem);
+            }
+        } finally {
+            cursor.close();
+        }
+
+        // Pre-decode all HTMLs
+        final long start = System.currentTimeMillis();
+        for (StreamItemEntry streamItem : streamItems) {
+            streamItem.decodeHtml(getContext());
+        }
+        final long end = System.currentTimeMillis();
+        if (DEBUG) {
+            Log.d(TAG, "Decoded HTML for " + streamItems.size() + " items, took "
+                    + (end - start) + " ms");
+        }
+
+        // Now retrieve any photo records associated with the stream items.
+        if (!streamItems.isEmpty()) {
+            if (result.isUserProfile()) {
+                // If the stream items we're loading are for the profile, we can't bulk-load the
+                // stream items with a custom selection.
+                for (StreamItemEntry entry : streamItems) {
+                    Cursor siCursor = getContext().getContentResolver().query(
+                            Uri.withAppendedPath(
+                                    ContentUris.withAppendedId(
+                                            StreamItems.CONTENT_URI, entry.getId()),
+                                    StreamItems.StreamItemPhotos.CONTENT_DIRECTORY),
+                            null, null, null, null);
                     try {
-                        while (sipCursor.moveToNext()) {
-                            long streamItemId = sipCursor.getLong(
-                                    sipCursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
-                            StreamItemEntry streamItem = streamItemsById.get(streamItemId);
-                            streamItem.addPhoto(new StreamItemPhotoEntry(sipCursor));
+                        while (siCursor.moveToNext()) {
+                            entry.addPhoto(new StreamItemPhotoEntry(siCursor));
                         }
                     } finally {
-                        sipCursor.close();
+                        siCursor.close();
                     }
                 }
-            }
-
-            // Set the sorted stream items on the result.
-            Collections.sort(streamItems);
-            result.mStreamItems.addAll(streamItems);
-        }
-
-        @Override
-        protected void onPostExecute(Result result) {
-            unregisterObserver();
-
-            // The creator isn't interested in any further updates
-            if (mDestroyed || result == null) {
-                return;
-            }
-
-            mContact = result;
-
-            if (result.isLoaded()) {
-                mLookupUri = result.getLookupUri();
-
-                if (!result.isDirectoryEntry()) {
-                    Log.i(TAG, "Registering content observer for " + mLookupUri);
-                    if (mObserver == null) {
-                        mObserver = new ForceLoadContentObserver();
+            } else {
+                String[] streamItemIdArr = new String[streamItems.size()];
+                StringBuilder streamItemPhotoSelection = new StringBuilder();
+                streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN (");
+                for (int i = 0; i < streamItems.size(); i++) {
+                    if (i > 0) {
+                        streamItemPhotoSelection.append(",");
                     }
-                    getContext().getContentResolver().registerContentObserver(
-                            mLookupUri, true, mObserver);
+                    streamItemPhotoSelection.append("?");
+                    streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId());
                 }
-
-                if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) {
-                    mContact.setLoadingPhoto(true);
-                    new AsyncPhotoLoader().execute(mContact.getPhotoUri());
+                streamItemPhotoSelection.append(")");
+                Cursor sipCursor = getContext().getContentResolver().query(
+                        StreamItems.CONTENT_PHOTO_URI,
+                        null, streamItemPhotoSelection.toString(), streamItemIdArr,
+                        StreamItemPhotos.STREAM_ITEM_ID);
+                try {
+                    while (sipCursor.moveToNext()) {
+                        long streamItemId = sipCursor.getLong(
+                                sipCursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID));
+                        StreamItemEntry streamItem = streamItemsById.get(streamItemId);
+                        streamItem.addPhoto(new StreamItemPhotoEntry(sipCursor));
+                    }
+                } finally {
+                    sipCursor.close();
                 }
+            }
+        }
 
-                // inform the source of the data that this contact is being looked at
-                postViewNotificationToSyncAdapter();
+        // Set the sorted stream items on the result.
+        Collections.sort(streamItems);
+        result.mStreamItems.addAll(streamItems);
+    }
+
+    @Override
+    public void deliverResult(Result result) {
+        unregisterObserver();
+
+        // The creator isn't interested in any further updates
+        if (isReset() || result == null) {
+            return;
+        }
+
+        mContact = result;
+
+        if (result.isLoaded()) {
+            mLookupUri = result.getLookupUri();
+
+            if (!result.isDirectoryEntry()) {
+                Log.i(TAG, "Registering content observer for " + mLookupUri);
+                if (mObserver == null) {
+                    mObserver = new ForceLoadContentObserver();
+                }
+                getContext().getContentResolver().registerContentObserver(
+                        mLookupUri, true, mObserver);
             }
 
-            deliverResult(mContact);
+            // inform the source of the data that this contact is being looked at
+            postViewNotificationToSyncAdapter();
         }
+
+        super.deliverResult(mContact);
     }
 
     /**
@@ -1200,50 +1207,6 @@
         }
     }
 
-    private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> {
-
-        private static final int BUFFER_SIZE = 1024*16;
-
-        @Override
-        protected byte[] doInBackground(String... params) {
-            Uri uri = Uri.parse(params[0]);
-            byte[] data = null;
-            try {
-                InputStream is = getContext().getContentResolver().openInputStream(uri);
-                if (is != null) {
-                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                    try {
-                        byte[] mBuffer = new byte[BUFFER_SIZE];
-
-                        int size;
-                        while ((size = is.read(mBuffer)) != -1) {
-                            baos.write(mBuffer, 0, size);
-                        }
-                        data = baos.toByteArray();
-                    } finally {
-                        is.close();
-                    }
-                } else {
-                    Log.v(TAG, "Cannot load photo " + uri);
-                }
-            } catch (IOException e) {
-                Log.e(TAG, "Cannot load photo " + uri, e);
-            }
-
-            return data;
-        }
-
-        @Override
-        protected void onPostExecute(byte[] data) {
-            if (mContact != null) {
-                mContact = new Result(mContact);
-                mContact.setPhotoBinaryData(data);
-                mContact.setLoadingPhoto(false);
-                deliverResult(mContact);
-            }
-        }
-    }
-
     private void unregisterObserver() {
         if (mObserver != null) {
             getContext().getContentResolver().unregisterContentObserver(mObserver);
@@ -1251,20 +1214,6 @@
         }
     }
 
-    public ContactLoader(Context context, Uri lookupUri) {
-        this(context, lookupUri, false, false, false);
-    }
-
-    public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData,
-            boolean loadStreamItems, boolean loadInvitableAccountTypes) {
-        super(context);
-        mLookupUri = lookupUri;
-        mRequestedUri = lookupUri;
-        mLoadGroupMetaData = loadGroupMetaData;
-        mLoadStreamItems = loadStreamItems;
-        mLoadInvitableAccountTypes = loadInvitableAccountTypes;
-    }
-
     /**
      * Sets whether to load stream items. Will trigger a reload if the value has changed.
      * At the moment, this is only used for debugging purposes
@@ -1296,15 +1245,15 @@
     }
 
     @Override
-    protected void onForceLoad() {
-        final LoadContactTask task = new LoadContactTask();
-        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
+    protected void onStopLoading() {
+        cancelLoad();
     }
 
     @Override
     protected void onReset() {
+        super.onReset();
+        cancelLoad();
         unregisterObserver();
         mContact = null;
-        mDestroyed = true;
     }
 }
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index f475609..6de361d 100644
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -727,7 +727,7 @@
         deliverCallback(callbackIntent);
     }
 
-    private void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd,
+    private static void addMembersToGroup(ContentResolver resolver, long[] rawContactsToAdd,
             long groupId) {
         if (rawContactsToAdd == null) {
             return;
@@ -762,9 +762,8 @@
                 }
 
                 // Apply batch
-                ContentProviderResult[] results = null;
                 if (!rawContactOperations.isEmpty()) {
-                    results = resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations);
+                    resolver.applyBatch(ContactsContract.AUTHORITY, rawContactOperations);
                 }
             } catch (RemoteException e) {
                 // Something went wrong, bail without success
@@ -780,7 +779,7 @@
         }
     }
 
-    private void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove,
+    private static void removeMembersFromGroup(ContentResolver resolver, long[] rawContactsToRemove,
             long groupId) {
         if (rawContactsToRemove == null) {
             return;
@@ -789,7 +788,7 @@
             // Apply the delete operation on the data row for the given raw contact's
             // membership in the given group. If no contact matches the provided selection, then
             // nothing will be done. Just continue to the next contact.
-            getContentResolver().delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " +
+            resolver.delete(Data.CONTENT_URI, Data.RAW_CONTACT_ID + "=? AND " +
                     Data.MIMETYPE + "=? AND " + GroupMembership.GROUP_ROW_ID + "=?",
                     new String[] { String.valueOf(rawContactId),
                     GroupMembership.CONTENT_ITEM_TYPE, String.valueOf(groupId)});
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index dd3b03b..ea9e116 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -100,7 +100,7 @@
         setContentView(R.layout.contact_detail_activity);
 
         mContactDetailLayoutController = new ContactDetailLayoutController(this, savedState,
-                getFragmentManager(), findViewById(R.id.contact_detail_container),
+                getFragmentManager(), null, findViewById(R.id.contact_detail_container),
                 mContactDetailFragmentListener);
 
         // We want the UP affordance but no app icon.
diff --git a/src/com/android/contacts/activities/DialtactsActivity.java b/src/com/android/contacts/activities/DialtactsActivity.java
index 8d57288..cdaaa68 100644
--- a/src/com/android/contacts/activities/DialtactsActivity.java
+++ b/src/com/android/contacts/activities/DialtactsActivity.java
@@ -809,6 +809,12 @@
             } else {
                 Log.e(TAG, "DialpadFragment isn't ready yet when the tab is already selected.");
             }
+        } else if (mViewPager.getCurrentItem() == TAB_INDEX_CALL_LOG) {
+            if (mCallLogFragment != null) {
+                mCallLogFragment.configureScreenFromIntent(newIntent);
+            } else {
+                Log.e(TAG, "CallLogFragment isn't ready yet when the tab is already selected.");
+            }
         }
         invalidateOptionsMenu();
     }
diff --git a/src/com/android/contacts/activities/DialtactsViewPager.java b/src/com/android/contacts/activities/DialtactsViewPager.java
deleted file mode 100644
index fb869a9..0000000
--- a/src/com/android/contacts/activities/DialtactsViewPager.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.contacts.activities;
-
-import android.content.Context;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-
-public class DialtactsViewPager extends ViewPager {
-    public DialtactsViewPager(Context context) {
-        super(context);
-    }
-
-    public DialtactsViewPager(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    /**
-     * ViewPager inherits ViewGroup's default behavior of delayed clicks
-     * on its children, but in order to make the dialpad more responsive we
-     * disable that here. The Call Log and Favorites tabs are both
-     * ListViews which delay their children anyway, as desired to prevent
-     * seeing pressed states flashing while scrolling lists
-     */
-    public boolean shouldDelayChildPressedState() {
-        return false;
-    }
-}
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index de08c4c..e41adad 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -60,6 +60,7 @@
 import com.android.contacts.util.Constants;
 import com.android.contacts.util.DialogManager;
 import com.android.contacts.util.PhoneCapabilityTester;
+import com.android.contacts.widget.TransitionAnimationView;
 
 import android.app.Fragment;
 import android.app.FragmentManager;
@@ -129,7 +130,6 @@
     private ActionBarAdapter mActionBarAdapter;
 
     private ContactDetailFragment mContactDetailFragment;
-    private ContactDetailUpdatesFragment mContactDetailUpdatesFragment;
 
     private ContactLoaderFragment mContactDetailLoaderFragment;
     private final ContactDetailLoaderFragmentListener mContactDetailLoaderFragmentListener =
@@ -160,7 +160,7 @@
 
     private View mFavoritesView;
     private View mBrowserView;
-    private View mDetailsView;
+    private TransitionAnimationView mDetailsView;
 
     private View mAddGroupImageView;
 
@@ -240,8 +240,6 @@
     public void onAttachFragment(Fragment fragment) {
         if (fragment instanceof ContactDetailFragment) {
             mContactDetailFragment = (ContactDetailFragment) fragment;
-        } else if (fragment instanceof ContactDetailUpdatesFragment) {
-            mContactDetailUpdatesFragment = (ContactDetailUpdatesFragment) fragment;
         } else if (fragment instanceof ContactsUnavailableFragment) {
             mContactsUnavailableFragment = (ContactsUnavailableFragment)fragment;
             mContactsUnavailableFragment.setProviderStatusLoader(mProviderStatusLoader);
@@ -418,7 +416,7 @@
 
             // Configure contact details
             mContactDetailLayoutController = new ContactDetailLayoutController(this, savedState,
-                    getFragmentManager(), findViewById(R.id.contact_detail_container),
+                    getFragmentManager(), mDetailsView, findViewById(R.id.contact_detail_container),
                     new ContactDetailFragmentListener());
         }
         transaction.commitAllowingStateLoss();
diff --git a/src/com/android/contacts/calllog/CallLogAdapter.java b/src/com/android/contacts/calllog/CallLogAdapter.java
index 99ba8e8..6105b03 100644
--- a/src/com/android/contacts/calllog/CallLogAdapter.java
+++ b/src/com/android/contacts/calllog/CallLogAdapter.java
@@ -352,7 +352,8 @@
         // view.
         NumberWithCountryIso numberCountryIso = new NumberWithCountryIso(number, countryIso);
         ContactInfo existingInfo = mContactInfoCache.getPossiblyExpired(numberCountryIso);
-        boolean updated = !info.equals(existingInfo);
+        boolean updated = (existingInfo != ContactInfo.EMPTY) && !info.equals(existingInfo);
+
         // Store the data in the cache so that the UI thread can use to display it. Store it
         // even if it has not changed so that it is marked as not expired.
         mContactInfoCache.put(numberCountryIso, info);
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index 059436b..39070e1 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -36,6 +36,7 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.CallLog.Calls;
@@ -84,6 +85,8 @@
     private boolean mCallLogFetched;
     private boolean mVoicemailStatusFetched;
 
+    private final Handler mHandler = new Handler();
+
     @Override
     public void onCreate(Bundle state) {
         super.onCreate(state);
@@ -106,10 +109,25 @@
         getActivity().invalidateOptionsMenu();
         if (mScrollToTop) {
             final ListView listView = getListView();
+            // The smooth-scroll animation happens over a fixed time period.
+            // As a result, if it scrolls through a large portion of the list,
+            // each frame will jump so far from the previous one that the user
+            // will not experience the illusion of downward motion.  Instead,
+            // if we're not already near the top of the list, we instantly jump
+            // near the top, and animate from there.
             if (listView.getFirstVisiblePosition() > 5) {
                 listView.setSelection(5);
             }
-            listView.smoothScrollToPosition(0);
+            // Workaround for framework issue: the smooth-scroll doesn't
+            // occur if setSelection() is called immediately before.
+            mHandler.post(new Runnable() {
+               @Override
+               public void run() {
+                   if (getActivity() == null || getActivity().isFinishing()) return;
+                   listView.smoothScrollToPosition(0);
+               }
+            });
+
             mScrollToTop = false;
         }
         mCallLogFetched = true;
@@ -172,10 +190,22 @@
         getListView().setItemsCanFocus(true);
     }
 
+    /**
+     * Based on the new intent, decide whether the list should be configured
+     * to scroll up to display the first item.
+     */
+    public void configureScreenFromIntent(Intent newIntent) {
+        // Typically, when switching to the call-log we want to show the user
+        // the same section of the list that they were most recently looking
+        // at.  However, under some circumstances, we want to automatically
+        // scroll to the top of the list to present the newest call items.
+        // For example, immediately after a call is finished, we want to
+        // display information about that call.
+        mScrollToTop = Calls.CONTENT_TYPE.equals(newIntent.getType());
+    }
+
     @Override
     public void onStart() {
-        mScrollToTop = true;
-
         // Start the empty loader now to defer other fragments.  We destroy it when both calllog
         // and the voicemail status are fetched.
         getLoaderManager().initLoader(EMPTY_LOADER_ID, null,
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index 08e8bfe..9f37899 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -61,7 +61,6 @@
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.AlphaAnimation;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import android.widget.ListView;
 import android.widget.TextView;
 
@@ -208,10 +207,6 @@
      */
     public static OnClickListener setPhoto(Context context, Result contactData,
             ImageView photoView, boolean expandPhotoOnClick) {
-        if (contactData.isLoadingPhoto()) {
-            photoView.setImageBitmap(null);
-            return null;
-        }
         byte[] photo = contactData.getPhotoBinaryData();
         Bitmap bitmap = photo != null ? BitmapFactory.decodeByteArray(photo, 0, photo.length)
                 : ContactBadgeUtil.loadDefaultAvatarPhoto(context, true, false);
@@ -354,61 +349,76 @@
 
     /** Creates the view that represents a stream item. */
     public static View createStreamItemView(LayoutInflater inflater, Context context,
-            StreamItemEntry streamItem, LinearLayout parent,
-            View.OnClickListener photoClickListener) {
-        View container = inflater.inflate(R.layout.stream_item_container, parent, false);
-        ViewGroup contentTable = (ViewGroup) container.findViewById(R.id.stream_item_content);
+            View convertView, StreamItemEntry streamItem, View.OnClickListener photoClickListener) {
 
-        ContactPhotoManager contactPhotoManager = ContactPhotoManager.getInstance(context);
-        List<StreamItemPhotoEntry> photos = streamItem.getPhotos();
+        // Try to recycle existing views.
+        final View container;
+        if (convertView != null) {
+            container = convertView;
+        } else {
+            container = inflater.inflate(R.layout.stream_item_container, null, false);
+        }
+
+        final ContactPhotoManager contactPhotoManager = ContactPhotoManager.getInstance(context);
+        final List<StreamItemPhotoEntry> photos = streamItem.getPhotos();
         final int photoCount = photos.size();
 
-        // This stream item only has text.
+        // Add the text part.
+        addStreamItemText(context, streamItem, container);
+
+        // Add images.
+        final ViewGroup imageRows = (ViewGroup) container.findViewById(R.id.stream_item_image_rows);
+
         if (photoCount == 0) {
-            View textOnlyContainer = inflater.inflate(R.layout.stream_item_row_text, contentTable,
-                    false);
-            addStreamItemText(context, streamItem, textOnlyContainer);
-            contentTable.addView(textOnlyContainer);
+            // This stream item only has text.
+            imageRows.setVisibility(View.GONE);
         } else {
-            // This stream item has text and photos. Process the photos, two at a time.
-            for (int index = 0; index < photoCount; index += 2) {
-                final StreamItemPhotoEntry firstPhoto = photos.get(index);
-                if (index + 1 < photoCount) {
-                    // Put in two photos, side by side.
-                    final StreamItemPhotoEntry secondPhoto = photos.get(index + 1);
-                    View photoContainer = inflater.inflate(R.layout.stream_item_row_two_images,
-                            contentTable, false);
-                    loadPhoto(contactPhotoManager, streamItem, firstPhoto, photoContainer,
-                            R.id.stream_item_first_image, photoClickListener);
-                    loadPhoto(contactPhotoManager, streamItem, secondPhoto, photoContainer,
-                            R.id.stream_item_second_image, photoClickListener);
-                    contentTable.addView(photoContainer);
-                } else {
-                    // Put in a single photo
-                    View photoContainer = inflater.inflate(
-                            R.layout.stream_item_row_one_image, contentTable, false);
-                    loadPhoto(contactPhotoManager, streamItem, firstPhoto, photoContainer,
-                            R.id.stream_item_first_image, photoClickListener);
-                    contentTable.addView(photoContainer);
+            // This stream item has text and photos.
+            imageRows.setVisibility(View.VISIBLE);
+
+            // Number of image rows needed, which is cailing(photoCount / 2)
+            final int numImageRows = (photoCount + 1) / 2;
+
+            // Actual image rows.
+            final int numOldImageRows = imageRows.getChildCount();
+
+            // Make sure we have enough stream_item_row_images.
+            if (numOldImageRows == numImageRows) {
+                // Great, we have the just enough number of rows...
+
+            } else if (numOldImageRows < numImageRows) {
+                // Need to add more image rows.
+                for (int i = numOldImageRows; i < numImageRows; i++) {
+                    View imageRow = inflater.inflate(R.layout.stream_item_row_images, imageRows,
+                            true);
+                }
+            } else {
+                // We have exceeding image rows.  Hide them.
+                for (int i = numImageRows; i < numOldImageRows; i++) {
+                    imageRows.getChildAt(i).setVisibility(View.GONE);
                 }
             }
 
-            // Add text, comments, and attribution if applicable
-            View textContainer = inflater.inflate(R.layout.stream_item_row_text, contentTable,
-                    false);
-            // Add extra padding between the text and the images
-            int extraVerticalPadding = context.getResources().getDimensionPixelSize(
-                    R.dimen.detail_update_section_between_items_vertical_padding);
-            textContainer.setPadding(textContainer.getPaddingLeft(),
-                    textContainer.getPaddingTop() + extraVerticalPadding,
-                    textContainer.getPaddingRight(),
-                    textContainer.getPaddingBottom());
-            addStreamItemText(context, streamItem, textContainer);
-            contentTable.addView(textContainer);
-        }
+            // Put images, two by two.
+            for (int i = 0; i < photoCount; i += 2) {
+                final View imageRow = imageRows.getChildAt(i / 2);
+                // Reused image rows may not visible, so make sure they're shown.
+                imageRow.setVisibility(View.VISIBLE);
 
-        if (parent != null) {
-            parent.addView(container);
+                // Show first image.
+                loadPhoto(contactPhotoManager, streamItem, photos.get(i), imageRow,
+                        R.id.stream_item_first_image, photoClickListener);
+                final View secondContainer = imageRow.findViewById(R.id.second_image_container);
+                if (i + 1 < photoCount) {
+                    // Show the second image too.
+                    loadPhoto(contactPhotoManager, streamItem, photos.get(i + 1), imageRow,
+                            R.id.stream_item_second_image, photoClickListener);
+                    secondContainer.setVisibility(View.VISIBLE);
+                } else {
+                    // Hide the second image, but it still has to occupy the space.
+                    secondContainer.setVisibility(View.INVISIBLE);
+                }
+            }
         }
 
         return container;
@@ -447,14 +457,12 @@
         ImageGetter imageGetter = new DefaultImageGetter(context.getPackageManager());
 
         // Stream item text
-        setDataOrHideIfNone(HtmlUtils.fromHtml(context, streamItem.getText(), imageGetter, null),
-                htmlView);
+        setDataOrHideIfNone(streamItem.getDecodedText(), htmlView);
         // Attribution
         setDataOrHideIfNone(ContactBadgeUtil.getSocialDate(streamItem, context),
                 attributionView);
         // Comments
-        setDataOrHideIfNone(HtmlUtils.fromHtml(context, streamItem.getComments(), imageGetter,
-                null), commentsView);
+        setDataOrHideIfNone(streamItem.getDecodedComments(), commentsView);
         return rootView;
     }
 
@@ -516,6 +524,15 @@
         }
     }
 
+    private static Html.ImageGetter sImageGetter;
+
+    public static Html.ImageGetter getImageGetter(Context context) {
+        if (sImageGetter == null) {
+            sImageGetter = new DefaultImageGetter(context.getPackageManager());
+        }
+        return sImageGetter;
+    }
+
     /** Fetcher for images from resources to be included in HTML text. */
     private static class DefaultImageGetter implements Html.ImageGetter {
         /** The scheme used to load resources. */
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index b0050c0..33e2ab4 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -42,7 +42,6 @@
 import com.android.contacts.util.DateUtils;
 import com.android.contacts.util.PhoneCapabilityTester;
 import com.android.contacts.util.StructuredPostalUtils;
-import com.android.contacts.widget.TransitionAnimationView;
 import com.android.internal.telephony.ITelephony;
 import com.google.common.annotations.VisibleForTesting;
 
@@ -104,7 +103,6 @@
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.BaseAdapter;
 import android.widget.Button;
-import android.widget.CheckBox;
 import android.widget.ImageView;
 import android.widget.ListAdapter;
 import android.widget.ListPopupWindow;
@@ -222,8 +220,6 @@
     private ArrayList<ViewEntry> mAllEntries = new ArrayList<ViewEntry>();
     private LayoutInflater mInflater;
 
-    private boolean mTransitionAnimationRequested;
-
     private boolean mIsUniqueNumber;
     private boolean mIsUniqueEmail;
 
@@ -418,11 +414,6 @@
             getActivity().invalidateOptionsMenu();
         }
 
-        if (mTransitionAnimationRequested) {
-            TransitionAnimationView.startAnimation(mView, mContactData == null);
-            mTransitionAnimationRequested = false;
-        }
-
         if (mContactData == null) {
             mView.setVisibility(View.INVISIBLE);
             mAllEntries.clear();
diff --git a/src/com/android/contacts/detail/ContactDetailLayoutController.java b/src/com/android/contacts/detail/ContactDetailLayoutController.java
index cd479ca..b79ccc0 100644
--- a/src/com/android/contacts/detail/ContactDetailLayoutController.java
+++ b/src/com/android/contacts/detail/ContactDetailLayoutController.java
@@ -20,7 +20,9 @@
 import com.android.contacts.NfcHandler;
 import com.android.contacts.R;
 import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
+import com.android.contacts.util.PhoneCapabilityTester;
 import com.android.contacts.util.UriUtils;
+import com.android.contacts.widget.TransitionAnimationView;
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
@@ -35,6 +37,7 @@
 import android.support.v4.view.ViewPager.OnPageChangeListener;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewPropertyAnimator;
 import android.view.animation.AnimationUtils;
 import android.widget.AbsListView;
 import android.widget.AbsListView.OnScrollListener;
@@ -51,6 +54,8 @@
     private static final int TAB_INDEX_DETAIL = 0;
     private static final int TAB_INDEX_UPDATES = 1;
 
+    private final int SINGLE_PANE_FADE_IN_DURATION = 275;
+
     /**
      * There are 3 possible layouts for the contact detail screen:
      * 1. TWO_COLUMN - Tall and wide screen so the 2 pages can be shown side-by-side
@@ -65,7 +70,8 @@
     private final LayoutInflater mLayoutInflater;
     private final FragmentManager mFragmentManager;
 
-    private View mViewContainer;
+    private final View mViewContainer;
+    private final TransitionAnimationView mTransitionAnimationView;
     private ContactDetailFragment mDetailFragment;
     private ContactDetailUpdatesFragment mUpdatesFragment;
 
@@ -79,7 +85,7 @@
     private final ContactDetailTabCarousel mTabCarousel;
     private final ContactDetailFragmentCarousel mFragmentCarousel;
 
-    private ContactDetailFragment.Listener mContactDetailFragmentListener;
+    private final ContactDetailFragment.Listener mContactDetailFragmentListener;
 
     private ContactLoader.Result mContactData;
     private Uri mContactUri;
@@ -91,8 +97,8 @@
     private LayoutMode mLayoutMode;
 
     public ContactDetailLayoutController(Activity activity, Bundle savedState,
-            FragmentManager fragmentManager, View viewContainer, ContactDetailFragment.Listener
-            contactDetailFragmentListener) {
+            FragmentManager fragmentManager, TransitionAnimationView animationView,
+            View viewContainer, ContactDetailFragment.Listener contactDetailFragmentListener) {
 
         if (fragmentManager == null) {
             throw new IllegalStateException("Cannot initialize a ContactDetailLayoutController "
@@ -105,8 +111,11 @@
         mFragmentManager = fragmentManager;
         mContactDetailFragmentListener = contactDetailFragmentListener;
 
+        mTransitionAnimationView = animationView;
+
         // Retrieve views in case this is view pager and carousel mode
         mViewContainer = viewContainer;
+
         mViewPager = (ViewPager) viewContainer.findViewById(R.id.pager);
         mTabCarousel = (ContactDetailTabCarousel) viewContainer.findViewById(R.id.tab_carousel);
 
@@ -239,17 +248,36 @@
     }
 
     public void setContactData(ContactLoader.Result data) {
-        final Boolean contactHadUpdates;
+        final boolean contactWasLoaded;
+        final boolean contactHadUpdates;
         if (mContactData == null) {
-            contactHadUpdates = null;
+            contactHadUpdates = false;
+            contactWasLoaded = false;
         } else {
             contactHadUpdates = mContactHasUpdates;
+            contactWasLoaded = true;
         }
         mContactData = data;
         mContactHasUpdates = !data.getStreamItems().isEmpty();
+
+        if (PhoneCapabilityTester.isUsingTwoPanes(mActivity)) {
+            // Tablet: If we already showed data before, we want to cross-fade from screen to screen
+            if (contactWasLoaded && mTransitionAnimationView != null) {
+                mTransitionAnimationView.startTransition(mViewContainer, mContactData == null);
+            }
+        } else {
+            // Small screen: We are on our own screen. Fade the data in, but only the first time
+            if (!contactWasLoaded) {
+                mViewContainer.setAlpha(0.0f);
+                final ViewPropertyAnimator animator = mViewContainer.animate();
+                animator.alpha(1.0f);
+                animator.setDuration(SINGLE_PANE_FADE_IN_DURATION);
+            }
+        }
+
         if (mContactHasUpdates) {
             showContactWithUpdates(
-                    contactHadUpdates != null && contactHadUpdates.booleanValue() == false);
+                    contactWasLoaded && contactHadUpdates == false);
         } else {
             showContactWithoutUpdates();
         }
diff --git a/src/com/android/contacts/detail/ContactLoaderFragment.java b/src/com/android/contacts/detail/ContactLoaderFragment.java
index ddccfe6..fd1f458 100644
--- a/src/com/android/contacts/detail/ContactLoaderFragment.java
+++ b/src/com/android/contacts/detail/ContactLoaderFragment.java
@@ -33,7 +33,6 @@
 import android.content.Loader;
 import android.media.RingtoneManager;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Bundle;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
diff --git a/src/com/android/contacts/detail/StreamItemAdapter.java b/src/com/android/contacts/detail/StreamItemAdapter.java
index 2d870b4..089cb07 100644
--- a/src/com/android/contacts/detail/StreamItemAdapter.java
+++ b/src/com/android/contacts/detail/StreamItemAdapter.java
@@ -104,7 +104,7 @@
                 manager.getAccountType(streamItem.getAccountType(), streamItem.getDataSet());
 
         final View view = ContactDetailDisplayUtils.createStreamItemView(
-                mInflater, mContext, streamItem, null,
+                mInflater, mContext, convertView, streamItem,
                 // Only pass the photo click listener if the account type has the photo
                 // view activity.
                 (accountType.getViewStreamItemPhotoActivity() == null) ? null : mPhotoClickListener
@@ -130,6 +130,12 @@
     }
 
     @Override
+    public int getViewTypeCount() {
+        // ITEM_VIEW_TYPE_HEADER and ITEM_VIEW_TYPE_STREAM_ITEM
+        return 2;
+    }
+
+    @Override
     public int getItemViewType(int position) {
         if (position == 0) {
             return ITEM_VIEW_TYPE_HEADER;
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index 02aa2dc..10e59fc 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -16,7 +16,6 @@
 
 package com.android.contacts.dialpad;
 
-
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -52,12 +51,12 @@
 import android.text.style.RelativeSizeSpan;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
@@ -87,7 +86,7 @@
         View.OnLongClickListener, View.OnKeyListener,
         AdapterView.OnItemClickListener, TextWatcher,
         PopupMenu.OnMenuItemClickListener,
-        View.OnTouchListener {
+        DialpadImageButton.OnPressedListener {
     private static final String TAG = DialpadFragment.class.getSimpleName();
 
     private static final boolean DEBUG = false;
@@ -96,6 +95,7 @@
 
     /** The length of DTMF tones in milliseconds */
     private static final int TONE_LENGTH_MS = 150;
+    private static final int TONE_LENGTH_INFINITE = -1;
 
     /** The DTMF tone volume relative to other sounds in the stream */
     private static final int TONE_RELATIVE_VOLUME = 80;
@@ -213,6 +213,9 @@
 
     @Override
     public void afterTextChanged(Editable input) {
+        // When DTMF dialpad buttons are being pressed, we delay SpecialCharSequencMgr sequence,
+        // since some of SpecialCharSequenceMgr's behavior is too abrupt for the "touch-down"
+        // behavior.
         if (SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {
             // A special sequence was entered, clear the digits
             mDigits.getText().clear();
@@ -465,23 +468,11 @@
     }
 
     private void setupKeypad(View fragmentView) {
-        // For numeric buttons, we rely on onTouchListener instead of onClickListener
-        // for faster event handling, while some other buttons since basically
-        // onTouch event conflicts with horizontal swipes.
-        fragmentView.findViewById(R.id.one).setOnTouchListener(this);
-        fragmentView.findViewById(R.id.two).setOnTouchListener(this);
-        fragmentView.findViewById(R.id.three).setOnTouchListener(this);
-        fragmentView.findViewById(R.id.four).setOnTouchListener(this);
-        fragmentView.findViewById(R.id.five).setOnTouchListener(this);
-        fragmentView.findViewById(R.id.six).setOnTouchListener(this);
-        fragmentView.findViewById(R.id.seven).setOnTouchListener(this);
-        fragmentView.findViewById(R.id.eight).setOnTouchListener(this);
-        fragmentView.findViewById(R.id.nine).setOnTouchListener(this);
-        fragmentView.findViewById(R.id.zero).setOnTouchListener(this);
-
-        // Buttons other than numeric ones should use onClick as usual.
-        fragmentView.findViewById(R.id.star).setOnClickListener(this);
-        fragmentView.findViewById(R.id.pound).setOnClickListener(this);
+        int[] buttonIds = new int[] { R.id.one, R.id.two, R.id.three, R.id.four, R.id.five,
+                R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.zero, R.id.star, R.id.pound};
+        for (int id : buttonIds) {
+            ((DialpadImageButton) fragmentView.findViewById(id)).setOnPressedListener(this);
+        }
 
         // Long-pressing one button will initiate Voicemail.
         fragmentView.findViewById(R.id.one).setOnLongClickListener(this);
@@ -572,6 +563,8 @@
                 (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
 
+        // Make sure we don't leave this activity with a tone still playing.
+        stopTone();
         synchronized (mToneGeneratorLock) {
             if (mToneGenerator != null) {
                 mToneGenerator.release();
@@ -690,6 +683,47 @@
     }
 
     private void keyPressed(int keyCode) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_1:
+                playTone(ToneGenerator.TONE_DTMF_1, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_2:
+                playTone(ToneGenerator.TONE_DTMF_2, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_3:
+                playTone(ToneGenerator.TONE_DTMF_3, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_4:
+                playTone(ToneGenerator.TONE_DTMF_4, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_5:
+                playTone(ToneGenerator.TONE_DTMF_5, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_6:
+                playTone(ToneGenerator.TONE_DTMF_6, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_7:
+                playTone(ToneGenerator.TONE_DTMF_7, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_8:
+                playTone(ToneGenerator.TONE_DTMF_8, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_9:
+                playTone(ToneGenerator.TONE_DTMF_9, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_0:
+                playTone(ToneGenerator.TONE_DTMF_0, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_POUND:
+                playTone(ToneGenerator.TONE_DTMF_P, TONE_LENGTH_INFINITE);
+                break;
+            case KeyEvent.KEYCODE_STAR:
+                playTone(ToneGenerator.TONE_DTMF_S, TONE_LENGTH_INFINITE);
+                break;
+            default:
+                break;
+        }
+
         mHaptic.vibrate();
         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
         mDigits.onKeyDown(keyCode, event);
@@ -715,12 +749,15 @@
     }
 
     /**
-     * We handle the key based on the DOWN event, but we wait till the UP event to play the local
-     * DTMF tone (to avoid playing a spurious tone if the user is actually doing a swipe...)
+     * When a key is pressed, we start playing DTMF tone, do vibration, and enter the digit
+     * immediately. When a key is released, we stop the tone. Note that the "key press" event will
+     * be delivered by the system with certain amount of delay, it won't be synced with user's
+     * actual "touch-down" behavior.
      */
     @Override
-    public boolean onTouch(View view, MotionEvent event) {
-        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+    public void onPressed(View view, boolean pressed) {
+        if (DEBUG) Log.d(TAG, "onPressed(). view: " + view + ", pressed: " + pressed);
+        if (pressed) {
             switch (view.getId()) {
                 case R.id.one: {
                     keyPressed(KeyEvent.KEYCODE_1);
@@ -762,82 +799,28 @@
                     keyPressed(KeyEvent.KEYCODE_0);
                     break;
                 }
+                case R.id.pound: {
+                    keyPressed(KeyEvent.KEYCODE_POUND);
+                    break;
+                }
+                case R.id.star: {
+                    keyPressed(KeyEvent.KEYCODE_STAR);
+                    break;
+                }
                 default: {
                     Log.wtf(TAG, "Unexpected onTouch(ACTION_DOWN) event from: " + view);
                     break;
                 }
             }
-        } else if (event.getAction() == MotionEvent.ACTION_UP) {
-            switch (view.getId()) {
-                case R.id.one: {
-                    playTone(ToneGenerator.TONE_DTMF_1);
-                    break;
-                }
-                case R.id.two: {
-                    playTone(ToneGenerator.TONE_DTMF_2);
-                    break;
-                }
-                case R.id.three: {
-                    playTone(ToneGenerator.TONE_DTMF_3);
-                    break;
-                }
-                case R.id.four: {
-                    playTone(ToneGenerator.TONE_DTMF_4);
-                    break;
-                }
-                case R.id.five: {
-                    playTone(ToneGenerator.TONE_DTMF_5);
-                    break;
-                }
-                case R.id.six: {
-                    playTone(ToneGenerator.TONE_DTMF_6);
-                    break;
-                }
-                case R.id.seven: {
-                    playTone(ToneGenerator.TONE_DTMF_7);
-                    break;
-                }
-                case R.id.eight: {
-                    playTone(ToneGenerator.TONE_DTMF_8);
-                    break;
-                }
-                case R.id.nine: {
-                    playTone(ToneGenerator.TONE_DTMF_9);
-                    break;
-                }
-                case R.id.zero: {
-                    playTone(ToneGenerator.TONE_DTMF_0);
-                    break;
-                }
-                default: {
-                    Log.wtf(TAG, "Unexpected onTouch(ACTION_UP) event from: " + view);
-                    break;
-                }
-            }
-        } else if (event.getAction() == MotionEvent.ACTION_CANCEL) {
-            // This event will be thrown when a user starts dragging the dialpad screen,
-            // intending horizontal swipe. The system will see the event after ACTION_DOWN while
-            // it won't see relevant ACTION_UP event anymore.
-            //
-            // Here, remove the last digit already entered in the last ACTION_DOWN event.
-            removeLastOneDigitIfPossible();
+        } else {
+            view.jumpDrawablesToCurrentState();
+            stopTone();
         }
-        return false;
     }
 
     @Override
     public void onClick(View view) {
         switch (view.getId()) {
-            case R.id.pound: {
-                playTone(ToneGenerator.TONE_DTMF_P);
-                keyPressed(KeyEvent.KEYCODE_POUND);
-                return;
-            }
-            case R.id.star: {
-                playTone(ToneGenerator.TONE_DTMF_S);
-                keyPressed(KeyEvent.KEYCODE_STAR);
-                return;
-            }
             case R.id.deleteButton: {
                 keyPressed(KeyEvent.KEYCODE_DEL);
                 return;
@@ -1090,14 +1073,25 @@
 
     /**
      * Plays the specified tone for TONE_LENGTH_MS milliseconds.
+     */
+    private void playTone(int tone) {
+        playTone(tone, TONE_LENGTH_MS);
+    }
+
+    /**
+     * Play the specified tone for the specified milliseconds
      *
      * The tone is played locally, using the audio stream for phone calls.
      * Tones are played only if the "Audible touch tones" user preference
      * is checked, and are NOT played if the device is in silent mode.
      *
+     * The tone length can be -1, meaning "keep playing the tone." If the caller does so, it should
+     * call stopTone() afterward.
+     *
      * @param tone a tone code from {@link ToneGenerator}
+     * @param durationMs tone length.
      */
-    void playTone(int tone) {
+    private void playTone(int tone, int durationMs) {
         // if local tone playback is disabled, just return.
         if (!mDTMFToneEnabled) {
             return;
@@ -1123,7 +1117,24 @@
             }
 
             // Start the new tone (will stop any playing tone)
-            mToneGenerator.startTone(tone, TONE_LENGTH_MS);
+            mToneGenerator.startTone(tone, durationMs);
+        }
+    }
+
+    /**
+     * Stop the tone if it is played.
+     */
+    private void stopTone() {
+        // if local tone playback is disabled, just return.
+        if (!mDTMFToneEnabled) {
+            return;
+        }
+        synchronized (mToneGeneratorLock) {
+            if (mToneGenerator == null) {
+                Log.w(TAG, "stopTone: mToneGenerator == null");
+                return;
+            }
+            mToneGenerator.stopTone();
         }
     }
 
diff --git a/src/com/android/contacts/dialpad/DialpadImageButton.java b/src/com/android/contacts/dialpad/DialpadImageButton.java
index 6e01379..a18cbb8 100644
--- a/src/com/android/contacts/dialpad/DialpadImageButton.java
+++ b/src/com/android/contacts/dialpad/DialpadImageButton.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.View;
 import android.widget.ImageButton;
 
 /**
@@ -29,6 +30,15 @@
  * the behavior.
  */
 public class DialpadImageButton extends ImageButton {
+    public interface OnPressedListener {
+        public void onPressed(View view, boolean pressed);
+    }
+
+    private OnPressedListener mOnPressedListener;
+
+    public void setOnPressedListener(OnPressedListener onPressedListener) {
+        mOnPressedListener = onPressedListener;
+    }
 
     public DialpadImageButton(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -39,11 +49,10 @@
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        final boolean ret = super.onTouchEvent(event);
-        if (event.getAction() == MotionEvent.ACTION_CANCEL) {
-            jumpDrawablesToCurrentState();
+    public void setPressed(boolean pressed) {
+        super.setPressed(pressed);
+        if (mOnPressedListener != null) {
+            mOnPressedListener.onPressed(this, pressed);
         }
-        return ret;
     }
 }
diff --git a/src/com/android/contacts/editor/Editor.java b/src/com/android/contacts/editor/Editor.java
index 423ca94..1151afd 100644
--- a/src/com/android/contacts/editor/Editor.java
+++ b/src/com/android/contacts/editor/Editor.java
@@ -85,4 +85,13 @@
      * Clears all fields in this {@link Editor}.
      */
     public void clearAllFields();
+
+    /**
+     * Called internally when the user has added a new field.  This
+     * allows the appropriate editor UI to be presented immediately.
+     * For example, if a new "event" is added, a date-picker will
+     * immediately pop up.
+     */
+    public void editNewlyAddedField();
+
 }
diff --git a/src/com/android/contacts/editor/EventFieldEditorView.java b/src/com/android/contacts/editor/EventFieldEditorView.java
index 475e172..08cbaef 100644
--- a/src/com/android/contacts/editor/EventFieldEditorView.java
+++ b/src/com/android/contacts/editor/EventFieldEditorView.java
@@ -92,6 +92,11 @@
     }
 
     @Override
+    public void editNewlyAddedField() {
+        showDialog(R.id.dialog_event_date_picker);
+    }
+
+    @Override
     protected void requestFocusForFirstEditField() {
         mDateView.requestFocus();
     }
diff --git a/src/com/android/contacts/editor/KindSectionView.java b/src/com/android/contacts/editor/KindSectionView.java
index eb8a0a7..8a01490 100644
--- a/src/com/android/contacts/editor/KindSectionView.java
+++ b/src/com/android/contacts/editor/KindSectionView.java
@@ -56,6 +56,8 @@
 
     private LayoutInflater mInflater;
 
+    private final ArrayList<Runnable> mRunWhenWindowFocused = new ArrayList<Runnable>(1);
+
     public KindSectionView(Context context) {
         this(context, null);
     }
@@ -291,6 +293,46 @@
         return true;
     }
 
+    /**
+     * Extends superclass implementation to also run tasks
+     * enqueued by {@link #runWhenWindowFocused}.
+     */
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+        if (hasWindowFocus) {
+            for (Runnable r: mRunWhenWindowFocused) {
+                r.run();
+            }
+            mRunWhenWindowFocused.clear();
+        }
+    }
+
+    /**
+     * Depending on whether we are in the currently-focused window, either run
+     * the argument immediately, or stash it until our window becomes focused.
+     */
+    private void runWhenWindowFocused(Runnable r) {
+        if (hasWindowFocus()) {
+            r.run();
+        } else {
+            mRunWhenWindowFocused.add(r);
+        }
+    }
+
+    /**
+     * Simple wrapper around {@link #runWhenWindowFocused}
+     * to ensure that it runs in the UI thread.
+     */
+    private void postWhenWindowFocused(final Runnable r) {
+        post(new Runnable() {
+            @Override
+            public void run() {
+                runWhenWindowFocused(r);
+            }
+        });
+    }
+
     public void addItem() {
         ValuesDelta values = null;
         // If this is a list, we can freely add. If not, only allow adding the first.
@@ -312,13 +354,15 @@
         }
 
         final View newField = createEditorView(values);
-        post(new Runnable() {
-
-            @Override
-            public void run() {
-                newField.requestFocus();
-            }
-        });
+        if (newField instanceof Editor) {
+            postWhenWindowFocused(new Runnable() {
+                @Override
+                public void run() {
+                    newField.requestFocus();
+                    ((Editor)newField).editNewlyAddedField();
+                }
+            });
+        }
 
         // Hide the "add field" footer because there is now a blank field.
         mAddFieldFooter.setVisibility(View.GONE);
diff --git a/src/com/android/contacts/editor/PhotoEditorView.java b/src/com/android/contacts/editor/PhotoEditorView.java
index db29544..d2e4340 100644
--- a/src/com/android/contacts/editor/PhotoEditorView.java
+++ b/src/com/android/contacts/editor/PhotoEditorView.java
@@ -63,6 +63,12 @@
         mFrameView.setEnabled(enabled);
     }
 
+    @Override
+    public void editNewlyAddedField() {
+        // Never called, since the user never adds a new photo-editor;
+        // you can only change the picture in an existing editor.
+    }
+
     /** {@inheritDoc} */
     @Override
     protected void onFinishInflate() {
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index 0919006..5b1dd5f 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -40,10 +40,12 @@
 import android.text.TextWatcher;
 import android.text.style.StyleSpan;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -56,6 +58,8 @@
  * and to correctly write any changes values.
  */
 public class TextFieldsEditorView extends LabeledEditorView {
+    private static final String TAG = TextFieldsEditorView.class.getSimpleName();
+
     private EditText[] mFieldEditTexts = null;
     private ViewGroup mFields = null;
     private View mExpansionViewContainer;
@@ -113,6 +117,22 @@
     }
 
     @Override
+    public void editNewlyAddedField() {
+        // Some editors may have multiple fields (eg: first-name/last-name), but since the user
+        // has not selected a particular one, it is reasonable to simply pick the first.
+        final View editor = mFields.getChildAt(0);
+
+        // Show the soft-keyboard.
+        InputMethodManager imm =
+                (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+        if (imm != null) {
+            if (!imm.showSoftInput(editor, InputMethodManager.SHOW_IMPLICIT)) {
+                Log.w(TAG, "Failed to show soft input method.");
+            }
+        }
+    }
+
+    @Override
     public void setEnabled(boolean enabled) {
         super.setEnabled(enabled);
 
diff --git a/src/com/android/contacts/group/SuggestedMemberListAdapter.java b/src/com/android/contacts/group/SuggestedMemberListAdapter.java
index 08401bf..30718de 100644
--- a/src/com/android/contacts/group/SuggestedMemberListAdapter.java
+++ b/src/com/android/contacts/group/SuggestedMemberListAdapter.java
@@ -295,6 +295,7 @@
 
         @Override
         protected void publishResults(CharSequence constraint, FilterResults results) {
+            @SuppressWarnings("unchecked")
             List<SuggestedMember> suggestionsList = (List<SuggestedMember>) results.values;
             if (suggestionsList == null) {
                 return;
diff --git a/src/com/android/contacts/interactions/PhoneNumberInteraction.java b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
index 401cbb1..3c3c347 100644
--- a/src/com/android/contacts/interactions/PhoneNumberInteraction.java
+++ b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
@@ -108,6 +108,7 @@
             this.mimeType    = in.readString();
         }
 
+        @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeLong(id);
             dest.writeString(phoneNumber);
@@ -118,10 +119,12 @@
             dest.writeString(mimeType);
         }
 
+        @Override
         public int describeContents() {
             return 0;
         }
 
+        @Override
         public boolean collapseWith(PhoneItem phoneItem) {
             if (!shouldCollapseWith(phoneItem)) {
                 return false;
@@ -130,6 +133,7 @@
             return true;
         }
 
+        @Override
         public boolean shouldCollapseWith(PhoneItem phoneItem) {
             return ContactsUtils.shouldCollapse(Phone.CONTENT_ITEM_TYPE, phoneNumber,
                     Phone.CONTENT_ITEM_TYPE, phoneItem.phoneNumber);
@@ -142,10 +146,12 @@
 
         public static final Parcelable.Creator<PhoneItem> CREATOR
                 = new Parcelable.Creator<PhoneItem>() {
+            @Override
             public PhoneItem createFromParcel(Parcel in) {
                 return new PhoneItem(in);
             }
 
+            @Override
             public PhoneItem[] newArray(int size) {
                 return new PhoneItem[size];
             }
@@ -243,7 +249,10 @@
                     .create();
         }
 
+        @Override
         public void onClick(DialogInterface dialog, int which) {
+            final Activity activity = getActivity();
+            if (activity == null) return;
             final AlertDialog alertDialog = (AlertDialog)dialog;
             if (mPhoneList.size() > which && which >= 0) {
                 final PhoneItem phoneItem = mPhoneList.get(which);
@@ -251,11 +260,11 @@
                 if (checkBox.isChecked()) {
                     // Request to mark the data as primary in the background.
                     final Intent serviceIntent = ContactSaveService.createSetSuperPrimaryIntent(
-                            getActivity(), phoneItem.id);
-                    getActivity().startService(serviceIntent);
+                            activity, phoneItem.id);
+                    activity.startService(serviceIntent);
                 }
 
-                PhoneNumberInteraction.performAction(getActivity(), phoneItem.phoneNumber,
+                PhoneNumberInteraction.performAction(activity, phoneItem.phoneNumber,
                         mInteractionType, mCallOrigin);
             } else {
                 dialog.dismiss();
diff --git a/src/com/android/contacts/list/ContactTileImageContainer.java b/src/com/android/contacts/list/ContactTileImageContainer.java
deleted file mode 100644
index e1a791b..0000000
--- a/src/com/android/contacts/list/ContactTileImageContainer.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.contacts.list;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-
-/**
- * Custom container for ImageView or ContactBadge inside {@link ContactTileView}.
- *
- * This improves the performance of favorite tabs by not passing the layout request to the parent
- * views, assuming that once measured this will not need to resize itself.
- */
-public class ContactTileImageContainer extends FrameLayout {
-
-    public ContactTileImageContainer(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    public void requestLayout() {
-        forceLayout();
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/contacts/util/StreamItemEntry.java b/src/com/android/contacts/util/StreamItemEntry.java
index 5959c46..6c8210f 100644
--- a/src/com/android/contacts/util/StreamItemEntry.java
+++ b/src/com/android/contacts/util/StreamItemEntry.java
@@ -16,10 +16,13 @@
 
 package com.android.contacts.util;
 
+import com.android.contacts.detail.ContactDetailDisplayUtils;
 import com.android.contacts.test.NeededForTesting;
 
+import android.content.Context;
 import android.database.Cursor;
 import android.provider.ContactsContract.StreamItems;
+import android.text.Html;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -36,6 +39,8 @@
     private final long mId;
     private final String mText;
     private final String mComments;
+    private CharSequence mDecodedText;
+    private CharSequence mDecodedComments;
     private final long mTimestamp;
     private final String mAccountType;
     private final String mAccountName;
@@ -136,6 +141,24 @@
         return mPhotos;
     }
 
+    public void decodeHtml(Context context) {
+        final Html.ImageGetter imageGetter = ContactDetailDisplayUtils.getImageGetter(context);
+        if (mText != null) {
+            mDecodedText = HtmlUtils.fromHtml(context, mText, imageGetter, null);
+        }
+        if (mComments != null) {
+            mDecodedComments = HtmlUtils.fromHtml(context, mComments, imageGetter, null);
+        }
+    }
+
+    public CharSequence getDecodedText() {
+        return mDecodedText;
+    }
+
+    public CharSequence getDecodedComments() {
+        return mDecodedComments;
+    }
+
     private static String getString(Cursor cursor, String columnName) {
         return cursor.getString(cursor.getColumnIndex(columnName));
     }
diff --git a/src/com/android/contacts/widget/LayoutSuppressingImageView.java b/src/com/android/contacts/widget/LayoutSuppressingImageView.java
new file mode 100644
index 0000000..d80aeea
--- /dev/null
+++ b/src/com/android/contacts/widget/LayoutSuppressingImageView.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+/**
+ * Custom {@link ImageView} that improves layouting performance.
+ *
+ * This improves the performance by not passing requestLayout() to its parent, taking advantage
+ * of knowing that image size won't change once set.
+ */
+public class LayoutSuppressingImageView extends ImageView {
+
+    public LayoutSuppressingImageView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void requestLayout() {
+        forceLayout();
+    }
+}
diff --git a/src/com/android/contacts/widget/LayoutSuppressingQuickContactBadge.java b/src/com/android/contacts/widget/LayoutSuppressingQuickContactBadge.java
new file mode 100644
index 0000000..3413e53
--- /dev/null
+++ b/src/com/android/contacts/widget/LayoutSuppressingQuickContactBadge.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.QuickContactBadge;
+
+/**
+ * Custom {@link QuickContactBadge} that improves layouting performance.
+ *
+ * This improves the performance by not passing requestLayout() to its parent, taking advantage
+ * of knowing that image size won't change once set.
+ */
+public class LayoutSuppressingQuickContactBadge extends QuickContactBadge {
+
+    public LayoutSuppressingQuickContactBadge(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void requestLayout() {
+        forceLayout();
+    }
+}
diff --git a/src/com/android/contacts/widget/TransitionAnimationView.java b/src/com/android/contacts/widget/TransitionAnimationView.java
index e2f8a87..c70ca25 100644
--- a/src/com/android/contacts/widget/TransitionAnimationView.java
+++ b/src/com/android/contacts/widget/TransitionAnimationView.java
@@ -25,12 +25,10 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.ViewParent;
 import android.widget.FrameLayout;
 
 /**
@@ -99,7 +97,7 @@
         if (mExitAnimation == null) {
             throw new IllegalArgumentException("Invalid exit animation: " + mExitAnimationId);
         }
-
+        mExitAnimation.setDuration(mAnimationDuration);
     }
 
     @Override
@@ -136,23 +134,7 @@
         }
     }
 
-    public static void startAnimation(View view, boolean closing) {
-        TransitionAnimationView container = null;
-        ViewParent parent = view.getParent();
-        while (parent instanceof View) {
-            if (parent instanceof TransitionAnimationView) {
-                container = (TransitionAnimationView) parent;
-                break;
-            }
-            parent = parent.getParent();
-        }
-
-        if (container != null) {
-            container.start(view, closing);
-        }
-    }
-
-    private void start(View view, boolean closing) {
+    public void startTransition(View view, boolean closing) {
         if (mEnterAnimation.isRunning()) {
             mEnterAnimation.end();
         }
@@ -172,11 +154,8 @@
                 return;
             }
 
+            mPreviousStateBitmap.eraseColor(Color.TRANSPARENT);
             Canvas canvas = new Canvas(mPreviousStateBitmap);
-            Paint paint = new Paint();
-            paint.setColor(Color.TRANSPARENT);
-            canvas.drawRect(0, 0, mPreviousStateBitmap.getWidth(), mPreviousStateBitmap.getHeight(),
-                    paint);
             canvas.clipRect(mClipRect);
             view.draw(canvas);
             canvas.setBitmap(null);
diff --git a/tests/src/com/android/contacts/detail/ContactDetailDisplayUtilsTest.java b/tests/src/com/android/contacts/detail/ContactDetailDisplayUtilsTest.java
index 3d383ff..fd30390 100644
--- a/tests/src/com/android/contacts/detail/ContactDetailDisplayUtilsTest.java
+++ b/tests/src/com/android/contacts/detail/ContactDetailDisplayUtilsTest.java
@@ -108,7 +108,7 @@
      */
     private View addStreamItemText(StreamItemEntry streamItem) {
         return ContactDetailDisplayUtils.addStreamItemText(getContext(), streamItem,
-                mLayoutInflater.inflate(R.layout.stream_item_row_text, null));
+                mLayoutInflater.inflate(R.layout.stream_item_container, null));
     }
 
     private StreamItemEntryBuilder getTestBuilder() {